Live data from Hacker News

APIs, robustness, and idempotency

stripe.com

1–10 of 52 posts

Re: APIs, robustness, and idempotency

#3
One thing about Stripe's API I have mixed feelings about is the liberal versioning. My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again. I know most of Stripes updates are "additive" such that they are backwards compatible if coded liberally, but it can be confusing. Same with Lob.

Re: APIs, robustness, and idempotency

#4
post #3

One thing about Stripe's API I have mixed feelings about is the liberal versioning. My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again. I know most of Stripes updates are "additive" such that they are backwards compatible if coded liberally, but it can be confusing. Same with Lob.

Stripe pin your API version once you have sent you first request, you can see it in the dashboard with an option to update to the latest api revision.

https://stripe.com/docs/upgrades

As to how they achieve it Amber has a good blog post over at http://amberonrails.com/move-fast-dont-break-your-api/ but it means it really is integrate once, and never touch it again :)

Re: APIs, robustness, and idempotency

#5
post #3

One thing about Stripe's API I have mixed feelings about is the liberal versioning. My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again. I know most of Stripes updates are "additive" such that they are backwards compatible if coded liberally, but it can be confusing. Same with Lob.

How is this confusing? You get locked into the version, and never have to worry about things breaking. If Stripe releases new features, you also get the new features. For most people, you just never have to think about versioning.

Re: APIs, robustness, and idempotency

#6
Curious that they don’t mention HTTP conditional requests [1] even in passing. This mechanism is typically used for slightly different things, but you can, for example, make a PATCH request “idempotent” (in their sense) by adding an If-Match header to it. I’d say that Idempotency-Key itself may be considered a precondition and used with status codes 412 [2] and 428 [3].

By the way, WebDAV extended this mechanism with a general If header [4] for all your precondition needs. I’m kinda glad it didn’t catch on though...

[1] https://tools.ietf.org/html/rfc7232

[2] https://tools.ietf.org/html/rfc7232#section-4.2

[3] https://tools.ietf.org/html/rfc6585#section-3

[4] https://tools.ietf.org/html/rfc4918#section-10.4

Re: APIs, robustness, and idempotency

#7
post #3

One thing about Stripe's API I have mixed feelings about is the liberal versioning. My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again. I know most of Stripes updates are "additive" such that they are backwards compatible if coded liberally, but it can be confusing. Same with Lob.

(I work at Stripe.)

API versioning is definitely a debatable subject, and I don't think that anyone at Stripe would claim that the current state of affairs is perfect by any means, but it's one that we think provides a good compromise between the stability of client integrations and our own ability to iterate on the API's design and make progress.

The classic problem with web APIs is that unless you have a good versioning scheme, once you've published them, you can never make a backward incompatible change (like removing a field) unless you're okay with breaking some people's integration. Especially when it comes to payments, people tend to have strong feelings about having their integrations broken, so we try to take as many precautions as possible to make sure that doesn't happen.

An approach to versioning that you'll see in many places is to do "major" API versioning where you do something like prefix your URLs with `/v1/` or send in a special `Accept` header. A problem with that approach though is that you'd need an incredibly good reason to ever build out a `/v2/` because if you ever bump that major version you're going to leave an incredible number of users behind on the original. Most people want to integrate one time and not have to worry about upgrading (ideally ever).

At Stripe, we've tried to build a compromise by introducing minor, date-based versions that include only a fairly constrained set of changes, but which we can bump more liberally. As others have mentioned here, your account gets locked into a version on its first request, and if never want to worry about API versioning at all, you can leave that version untouched essentially indefinitely.

If we realize that we made an API design mistake somewhere, we can fix it relatively easily and keep the API's design more cohesive for new users, while also leaving current users unaffected. It's also much easier to maintain for us because we only have to build a small compatibility module instead of having to maintain two (or more) completely divergent major API versions.

Anyway, I hope that helps explain some of the thinking behind this versioning scheme :)

Re: APIs, robustness, and idempotency

#8
post #6

Curious that they don’t mention HTTP conditional requests [1] even in passing. This mechanism is typically used for slightly different things, but you can, for example, make a PATCH request “idempotent” (in their sense) by adding an If-Match header to it. I’d say that Idempotency-Key itself may be considered a precondition and used with status codes 412 [2] and 428 [3]. By the way, WebDAV extended this mechanism with…

(I wrote this.)

It's always a bit of a fine line as to what makes the final cut in this sort of article (I tried to stay on message without getting too off track), but HTTP conditional requests are definitely something that could have been a good fit.

I should point out though that using `ETag`/`If-Match` generally has a slightly different use on updates compared to Stripe's `Idempotency-Key`. A server sends back an `ETag` that's correlated to the current state of a resource, and clients make conditional requests using one so that they can get a guarantee that they're not changing state where they didn't expect to.

Because every HTTP request stands by itself, it's very possible for a client to fetch a resource and go to update it on a second request only to accidentally clobber changes that were made by a different client. It's this sort of "mid-air collision" that `ETag`/`If-Match` help to avoid. Mozilla's documentation on the subject is quite good:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ET...

Re: APIs, robustness, and idempotency

#9
I authored this article and just wanted to leave a quick note on here that I'm more than happy to answer any questions, or debate/discuss the finer points of HTTP and API semantics ;)

An ex-colleague pointed out to me on Twitter today that there are other APIs out there that have developed a concept similar to Stripe's `Idempotency-Key` header, the "client tokens" used in EC2's API for example [1]. To my knowledge there hasn't really been a concerted effort to standardize such an idea more widely, but I might be wrong about that.

[1] http://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_In...

Re: APIs, robustness, and idempotency

#10
post #3

One thing about Stripe's API I have mixed feelings about is the liberal versioning. My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again. I know most of Stripes updates are "additive" such that they are backwards compatible if coded liberally, but it can be confusing. Same with Lob.

My experiencing with 100s of payment integrations is that they get done once and hopefully never touched again.

This is basically the only way to work with any payment service integration, in my experience.

A lot of these services concentrate on documenting their current versions but not historical ones, so maintenance of integrations using older versions is unnecessarily difficult, even if you only want to use other features that were already supported at your current version.

It's often also painful to update to a more recent revision, with limited documentation or tools for identifying breaking API changes and how to convert your integration systematically to work with the newer version. You basically have to do a full rewrite against the new API from the start, and of course that probably comes with a significant risk of regressions in other areas.

In some cases, there are also confusing and/or poorly documented rules about which version you actually get both on active requests to the API and in any webhooks you get back, depending on what's pinned to what, whether you've updated a default version in a dashboard somewhere, whether you send any extra headers with your request, whether any testing/sandbox environments are linked to the same version as production, etc.

It's all very unfortunate, because every payment service we use has added potentially useful new features since we first integrated, and using those features would probably bring in more revenues for us and by extension more fees for the payment services. However, the risk of breaking such an important part of our system is just too great for us.

Post reply on HN