
A zero-dependency edge backend
On this page
I dropped the official SDKs from an edge backend and called the vendor APIs directly with fetch. Not to be clever. The bundle was cold-start sensitive and the SDKs were heavy.
It worked. It also handed me a list of chores the SDK used to do quietly.
Raw fetch, by hand#
Both integrations, payments and transactional email, run on a Workers-style runtime where every kilobyte and every millisecond of cold start counts. The official SDKs bundle a lot to be convenient everywhere. I did not need everywhere. I needed two endpoints.
So the calls go straight to the REST APIs. The awkward part is the request body. These vendors take deeply nested, form-encoded parameters, and without the SDK you build that string yourself. Nested keys, bracket notation, the lot.
It looks like this in spirit:
line_items[0][price_data][unit_amount]=1500line_items[0][quantity]=1Fiddly, but honest. There is no magic in the SDK doing this. It is just string assembly, and now I can see all of it.
The chores the SDK was hiding#
Here is the trade. The moment you drop the SDK, you own reliability. Nobody is retrying for you. Nobody is timing out a hung socket for you.
So I added the missing pieces deliberately:
- An explicit request timeout, so a slow upstream cannot hang the whole invocation.
- Exactly one retry, and only on a network error or a 5xx. Transient failures deserve a second shot.
- Never a retry on a 4xx. Creating a checkout session is only idempotent-safe to retry when the failure was transient. A 4xx means the request itself was wrong, and repeating a wrong request just risks a duplicate, and a duplicate here is a second charge on a customer’s card, the kind of bug they notice on their bank statement before you do.
That distinction is the whole game. “Retry on failure” is not a policy. “Retry on transient failure, never on a rejected request” is.
On a size-sensitive runtime a well-scoped raw HTTP call can beat a full SDK. But you now own timeouts, retries, and error mapping, so be deliberate.
Errors: two audiences#
The last chore is error handling, and it has two audiences that want opposite things.
The user gets a generic message. Something went wrong, try again. They do not need the upstream’s internal error code, and honestly it would only worry them.
The logs get the truth. The real upstream status, the real message, enough to debug at three in the afternoon when a card processor is having a bad day. That split, generic out, specific in, is not laziness. It is the same instinct as the price problem: do not leak more than the other side needs.
You inherit the work, you don’t drop it#
An SDK is a bundle of good defaults. When you remove it you are not removing the work, you are inheriting it.
That can absolutely be worth it. On this runtime it was. But go in with eyes open: write the timeout, write the single careful retry, and decide on purpose which failures are safe to repeat. Transient, yes. A rejected request, no.
If you cannot answer “is this safe to retry” for a given call, you are not ready to drop the SDK for it yet.


