If I had a dollar for every crypto payments startup pitched as "Stripe for crypto", I could pay for a very large gas tab on Ethereum mainnet.
The phrase is doing real work, though. When someone says "Stripe for crypto", they usually mean one specific thing: I want my payments integration to feel boring. Not crypto-boring (where "boring" means "the chain didn't fork this week"). Stripe-boring. Idempotency-keys- just-work, the-docs-have-curl-snippets, the-test-mode-is-actually- useful boring.
This post is the seven things we measure ourselves against — the concrete commitments we believe a developer-first crypto payments platform has to make. Hold us to them.
1. Time-to-first-webhook in under 10 minutes#
The single best metric for developer experience is how long it takes a new account to receive their first webhook in their own code. Not "first dashboard view". Not "first signup". First webhook.
Our internal P50 is 6 minutes 40 seconds from signup to first
sandbox payment_intent.succeeded event landing on a localhost
ngrok tunnel. Our P90 is under 15 minutes. We log every signup-to-
first-webhook gap and the worst ones get root-caused like incidents.
Stripe-comparable benchmarks here are about 8 minutes. We're not satisfied with parity — we want our quickstart to be faster than Stripe's because crypto already has more cognitive load.
2. Test mode that is actually useful#
Sandbox is not "production with feature flags". Sandbox is a fully isolated environment with:
- Free, instant test transactions (no waiting for testnet faucets)
- A fake clock you can advance to test expiry, retries, sweeps
- A "force webhook event" button so you can replay any event into your endpoint at will
- Test KYC that always succeeds (and a separate test mode where it always fails, so you can test the rejection path)
- Sandbox API keys are free, never expire, and don't need a card
If your test environment requires a Discord ping to get testnet tokens, your developer experience is broken. Ours doesn't.
3. The API is the source of truth#
Webhooks are accelerators. They are not the source of truth.
Every state we publish via webhook is also retrievable via GET /v1/payment-intents/{id} with the same shape and the same field
semantics. If your endpoint goes down for an hour, the recovery path
is "list the events you missed and re-process them" — not a support
ticket.
This sounds obvious until you've integrated a payments provider where
the webhook payload is the only place you can ever see the
finalized_at timestamp. We promised ourselves day one we wouldn't
ship that.
4. Idempotency on every mutating endpoint#
Every POST accepts (and respects) an Idempotency-Key header. The
same key with the same body returns the cached response. The same key
with a different body returns 409, because that's almost always a
bug in your code.
We store keys for 24 hours. If you're building a retry policy that takes longer than 24 hours, you have bigger problems.
5. SDKs are real, not generated stubs#
A typed SDK that wraps fetch and lets you go to production isn't an
SDK; it's an HTTP client. A real SDK:
- Implements idempotency-key generation by default
- Handles webhook signature verification with one helper
- Surfaces typed error classes (
InsufficientBalance,AddressBlacklisted,RateLimited— notstring) - Auto-retries network errors with exponential backoff and respects
Retry-After - Comes with type definitions every event payload your handler will receive
We ship official SDKs for TypeScript, Python, Go and Ruby, all
hand-maintained. The TypeScript SDK ships its types from a single
shared OpenAPI spec, so the dashboard, the docs and your app are all
talking about the same PaymentIntent.
6. Errors that tell you what to do next#
A 400 response that says "invalid" is not an API; it's a riddle.
Every BchainPay error response includes:
{
"error": {
"code": "invalid_destination_address",
"message": "Address F8…3a is not valid for chain solana.",
"param": "withdrawal.destination",
"doc_url": "https://docs.bchainpay.com/errors#invalid_destination_address",
"request_id": "req_01HV…"
}
}code is stable and matches the SDK error class. param points at
the bad input. doc_url opens directly to the error page (which is a
real page with examples, not a 404). request_id is what you give
support and they can pull the full request trace.
7. Pricing that fits on a Post-it#
If pricing requires a sales call to estimate, it's not developer- first. Our entire pricing page is one number — 0.7% per successful payment — plus passed-through gas on withdrawals. No monthly fees, no setup fees, no "contact us for enterprise" until you're past volumes that genuinely need an SRE conversation.
What "developer-first" is not#
While we're being opinionated:
- It's not "we have a Postman collection". Postman is table stakes. Postman is not a product strategy.
- It's not "we have a TypeScript types package". Types without runtime guarantees are theatre.
- It's not "DevRel writes blog posts". Blog posts are useful only if the API behind them is good. Otherwise they're elaborate apologies.
- It's not "we shipped an SDK in Rust". Pick the languages your customers actually ship in and do those well. Three real SDKs beat fifteen generated ones.
Hold us to it#
The bar above isn't a wishlist; it's our scorecard. If you ever feel like one of those seven principles isn't holding up, email me directly: cipher@bchainpay.com. We measure DevX the way other teams measure uptime, and we treat regressions the same way: as incidents.
If you've integrated payments before, you know that the difference between "this took an afternoon" and "this took a sprint" is mostly about API design, not feature surface. We've optimized BchainPay for the afternoon.
The free sandbox is one click away. Go time it yourself.