---
title: "Stablecoin API sandbox vs production: what testing misses"
description: "Most stablecoin API sandboxes pass every integration test and still leave a team unprepared for production, because webhook delivery and idempotent retries are exactly what sandboxes fake or skip."
date: "2026-08-27"
category: "payments"
faq:
  - q: "Why does a stablecoin API sandbox pass but production still break?"
    a: "Sandboxes validate request and response shapes. The failure paths that only surface under real load, webhook retries and idempotency key expiry chief among them, need separate testing. A sandbox that returns clean 200s on every call can still hide a webhook handler that has never processed a redelivered event."
  - q: "Does Bridge's sandbox send webhooks?"
    a: "No. Bridge states this plainly in its own docs: no webhook covering payments activity fires while a developer is in sandbox, and Bridge recommends validating webhook handling directly in production instead."
  - q: "How long is a Bridge idempotency key valid for?"
    a: "24 hours. After that window, reusing the key returns a 422 error. Changing the request body and reusing the key inside the window produces a second, separate error that Bridge's docs never label with its own status code."
  - q: "What does 'at least once' webhook delivery mean for an integration?"
    a: "A single event can reach a webhook endpoint more than once, for instance if the first delivery attempt times out or the server briefly returns a non-2xx response. The handler has to produce the same result whether it processes that event once or several times. Circle's webhooks, for example, are signed and explicitly documented as [at-least-once](https://developers.circle.com/api-reference/webhooks), and Circle expects integrators to key their dedup logic off the notification ID rather than trust that each delivery is unique."
  - q: "Does BlindPay's sandbox send real webhooks?"
    a: "Yes. Development instances differ from production only in which API key signs the request. The events fired and their shapes stay identical, and verification works the same way too, so nothing about the handler needs to change at go-live."
  - q: "What should a team test before moving a stablecoin integration to production?"
    a: "Send a duplicate idempotency key and confirm the client handles the rejection, force a webhook redelivery and confirm the handler doesn't double-apply it, simulate a failed and a refunded transfer, and confirm dedup logic behaves the same way in sandbox as it will in production."
---

Bridge's own sandbox documentation states it outright: no payments webhook fires while a developer is in sandbox. Most evaluation checklists never catch that gap, because they test whether auth works and whether the response shape matches the docs. Almost none test what happens when a webhook arrives twice, or when a retry hits an idempotency key three seconds after it expired. A sandbox can return a clean 200 on every call and still never once have delivered a webhook to the server, or forced retry logic against an expired key. A green checklist says little about whether an integration survives its first bad day of production traffic.

## What Bridge's own docs admit sandbox can't do

Bridge is the stablecoin orchestration company Stripe acquired, and its docs state this directly: sandbox fires zero payments-related webhooks. There is no testnet or blockchain connectivity in sandbox either, and the environment is subject to what Bridge calls arbitrary rate limits, meaning it may drop requests without warning. Bridge's own recommendation, in [its quickstart documentation](https://apidocs.bridge.xyz/get-started/introduction/quick-start/setting-up-sandbox), is to use sandbox for schema validation and do the rest of the testing in production.

That recommendation has a direct implication. A team that writes its webhook handler against sandbox and ships it has never seen that handler run against production traffic, because sandbox never sent it one. The first webhook it processes arrives after go-live, against a live customer's live transfer. If the handler has a bug, that bug becomes an incident involving somebody's money, not a failed test in CI.

The same gap likely exists elsewhere in the category, though this has not been audited across every competitor's docs the way Bridge documents its own limits, and few providers are this direct about what their sandbox skips. A provider can have solid production infrastructure and a sandbox that tests almost none of it, and a team evaluating providers by reading the docs alone has no way to tell the difference until it switches to a production key.

## Idempotency key expiry is where retry logic breaks

Every serious [stablecoin API](/resources/more/what-is-a-stablecoin-api) requires an idempotency key on requests that move money or trigger something irreversible, because retrying a POST over a flaky connection should never risk creating the same transfer twice. The mechanism is universal. The expiry behavior is not, and that gap is where naive retry logic breaks.

Bridge documents a [24-hour idempotency window](https://apidocs.bridge.xyz/api-reference/introduction/idempotence). Reuse the same key after 24 hours and the result is a 422 Unprocessable Entity: the key has expired and the request is treated as brand new. Circle runs a comparable pattern, a required Idempotency-Key header (UUID v4) on mutating endpoints, plus a separate [idempotencyKey field inside payout request bodies](https://developers.circle.com/api-reference/cpn/managed-payments/payouts/create-payout).

Idempotency is not one behavior. It splits into different outcomes depending on what changed since the first request. Reuse a key exactly as sent and Bridge replays the original response, with no new side effect triggered. Reuse it after the 24-hour window closes, or reuse it inside that window with a changed body, and the result is an error instead: a fresh 422 in the first case, an unlabeled idempotency error in the second. Bridge's docs describe that second error without giving it its own status code, because the key no longer matches the request it originally guarded. A retry loop that only branches on 2xx versus non-2xx collapses both error cases into "failed, try again," which is exactly how a stale key ends up firing a duplicate transfer.

BlindPay follows a similar pattern. Every mutating v1 endpoint, payins and payouts included, accepts an opt-in Idempotency-Key header. A repeated key returns the original response, with no duplicate action triggered. The terms-of-service acceptance endpoint handles idempotency on its own: it takes a separate idempotency_key UUID, and reusing that one is rejected outright. The exact expiry window on the general header, and whether a changed body against a reused key throws a distinct conflict error the way Bridge's does, are not yet public. Check the current [API reference](/docs/api/reference) for the endpoint in question before assuming it matches another provider's behavior.

## Webhook delivery is at-least-once across the industry

Every stablecoin API webhook system worth using assumes the same thing: an endpoint might receive the same event more than once, and handling that safely is on the integrator. That is called at-least-once delivery. It exists because the alternative, guaranteeing exactly-once delivery over an unreliable network, is a much harder distributed-systems problem, and none of the providers covered here claim to have solved it. Providers retry on anything that is not a clean 2xx response, so a slow database write on the receiving end that causes a timeout looks, from the provider's side, exactly like a dropped request that needs retrying.

Circle's implementation is a useful reference for what careful design looks like here. Every webhook is [signed with ECDSA over P-256](https://developers.circle.com/api-reference/verify-webhook-signatures), and the public verification key comes from Circle's API by key ID rather than something hardcoded into the receiving app. That signature travels in an X-Circle-Signature header, checked before the payload is trusted. Circle's [documentation states outright that delivery is at least once](https://developers.circle.com/api-reference/webhooks), and it tells integrators to deduplicate on the Notification ID before applying any side effect. In practice, a handler's job is to check whether it has already processed that ID and do nothing if it has.

BlindPay runs its webhooks through Svix, and the mechanics land in the same place as Circle's. A svix-id header stays constant across every redelivery attempt of the same event. The svix-timestamp and svix-signature pair is verified with HMAC-SHA256 before acting on the payload, and if an endpoint does not return a 2xx, Svix retries with backoff over the following hours. Deduplicating on svix-id covers the case, and a handler already built for Circle's Notification ID pattern needs little more than a header rename to work against BlindPay's.

One part of this has no clean answer. Neither Circle's nor Bridge's public docs, as of this writing, state a maximum retry window, so there is no single number to size a dedup store against. A naive in-memory set of notification IDs that gets wiped on every deploy loses that history the moment a service redeploys, which is exactly when a provider might still be retrying an event from before the restart. The safer posture is a persistent store with a retention window longer than whatever retry period a provider does document. How much longer is a judgment call, and neither company answers it directly.

## What to check before a production key goes live

Run this checklist against a sandbox before a production key goes live. It groups by what kind of failure it catches: money movement or notifications.

On the money-movement side: replay a request with an idempotency key already used, and confirm the client branches on the error rather than retrying it blindly as a network failure. Set request amounts that force a failed transfer and a refunded one, then watch what the reconciliation logic does when a payment sits outside the happy path longer than expected.

On the notification side: trigger a webhook redelivery (most dashboards with an events log allow replaying a specific event manually), and confirm the handler recognizes the repeated ID, skips the side effect, and does not credit an account twice or fire a downstream notification twice. Confirm sandbox and production environments enforce the same dedup and retry rules. A sandbox that silently skips retries will never surface a bug that only shows up once backoff kicks in.

BlindPay's development instances are built so that last check actually holds. Webhooks fire with the same event names and payload shapes in development as in production, and signature verification works the same way too. Payins on a development instance auto-complete around 30 seconds after creation, so a full event can be observed landing rather than guessed at from documentation. Specific outcomes can also be forced by setting the request amount to 666.00 for a failed transfer or 777.00 for a refunded one, which is what "simulate a stalled transfer" means in practice: two API calls with different amounts. Every instance gets its own API key, so switching from a development key to a production one at go-live does not touch a single line of webhook-handling code, since the code was already exercised against the exact contract production uses.

Running that checklist against a current sandbox, on whatever provider is in use, surfaces the gap fast: if any of the four checks cannot run before a production key exists, that is the gap the sandbox is not reporting. For a broader view of what to evaluate across providers before committing to one, see the [comparison of stablecoin APIs](/resources/more/best-stablecoin-apis-2026). To run this checklist against BlindPay's sandbox, start with the [getting started guide](/docs/getting-started/overview), or [talk to the team](/contact) about a specific corridor.
