Webhooks overview
How MintCash notifies your server when state changes. Delivery semantics, retry policy, and what your endpoint needs to handle.
Webhooks are how MintCash tells you that something changed asynchronously — a payment succeeded, a subscription renewed, a refund cleared. Your endpoint receives an HMAC-signed POST; you verify the signature, dedupe the event, and act on it.
The contract
- Transport: HTTPS POST to the
callbackUrlyou pass onPOST /paymentsorPOST /subscriptions. The first time we see a URL it's registered as a webhook endpoint for your merchant; payments created with acallbackUrldeliver there, and payments created without one fall back to your most recently registered endpoint. - Body: JSON, with the same envelope shape across all events
- Signature: HMAC-SHA256 in the
x-signatureheader, computed over the raw request body using a secret unique to your merchant. - At-least-once delivery: we retry on non-2xx until we give up (see below). Your handler must be idempotent.
- Order: not guaranteed. A
succeededwebhook can arrive before apendingone. Handle out-of-order events defensively.
What you must do
In code form:
export async function POST(req: Request) {
const body = await req.text();
const signature = req.headers.get("x-signature");
if (!verifyWebhook(body, signature, signingSecret)) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(body);
if (event.environment !== process.env.MINTCASH_ENV) {
return new Response("wrong environment", { status: 400 });
}
if (await alreadyProcessed(event.eventId)) {
return new Response("ok", { status: 200 });
}
await handleEvent(event);
await markProcessed(event.eventId);
return new Response("ok", { status: 200 });
}Retry policy
When your endpoint returns non-2xx (or the request errors), MintCash retries the delivery up to 5 times — 6 attempts in total — with exponential backoff (the spacing widens with each attempt). Once the retries are exhausted the event is marked failed. The eventId stays the same across every redelivery of an event, so your dedupe key holds.
Latency budget
Aim to respond inside 10 seconds. A delivery that hangs ties up the attempt until our delivery infrastructure kills it, and it's then retried like any other failure.
If your handler does heavy work (e.g. sending emails, calling other APIs, rebuilding caches), defer that to a background job and return 200 immediately after persisting the event.
Idempotency is non-negotiable
Even if your endpoint is fast and reliable, you'll still see duplicate events
occasionally — a transient network blip on our side, a retry that crossed
paths with the original, a manual replay. Dedupe by eventId or you'll
double-fulfil orders.
Where to go next
- Event types — every event MintCash emits, with when it fires
- Payload reference — the JSON shape of each event
- Signature verification — code samples for verifying
x-signaturesafely