Skip to content

POST /v1/webhooks/stripe

Call POST /v1/webhooks/stripe
Price free
Scope none (public endpoint)
Operation id receiveStripeWebhook

Stripe calls this endpoint; agents never do. Operator setup: in the Stripe dashboard (test mode for staging, live mode for production) add a webhook endpoint with this URL, subscribe it to checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed and checkout.session.expired, and store its signing secret (whsec_...) in Doppler as STRIPE_WEBHOOK_SECRET. Verdict verifies Stripe-Signature over the raw body before parsing anything (401 unauthorized on failure, nothing stored), reads the organization from the event’s metadata.org_id and checks it against the pending pack named by metadata.pack_id (400 invalid_request when no organization resolves, nothing stored), records the event id, and credits the pack once when payment is confirmed. Idempotent on the Stripe event id: a replay returns 200 with duplicate: true and credits nothing; the same id with a different payload is refused with 400. Other event types are recorded and ignored. Free.

Copy it as curl. Set VERDICT_API_KEY first; uuidgen makes a fresh Idempotency-Key.

Request
curl -X POST 'https://api.askverdict.io/v1/webhooks/stripe' \
-H 'Stripe-Signature: t=1794060000,v1=5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e' \
-H 'Content-Type: application/json' \
-d '{
"id": "evt_1QExampleCheckoutCompleted",
"object": "event",
"type": "checkout.session.completed",
"livemode": false,
"data": {
"object": {
"id": "cs_test_example",
"object": "checkout.session",
"mode": "payment",
"payment_status": "paid",
"amount_total": 5000,
"currency": "usd",
"client_reference_id": "org_01JC3Z4Y8R9K2M5N7P1Q3S5T7V",
"metadata": {
"org_id": "org_01JC3Z4Y8R9K2M5N7P1Q3S5T7V",
"pack_id": "pck_01JC3Z4Y8R9K2M5N7P1Q3S5T7V",
"kind": "pack"
}
}
}
}'

200: The event was verified and recorded.

200 response
{
"received": true,
"duplicate": false
}
Name In Type Required Description
stripe-signature header string yes Stripe’s signature over the raw body (t=<unix seconds>,v1=<hex HMAC-SHA256>), computed with the endpoint’s signing secret. A missing or wrong signature, or a timestamp more than 5 minutes off, is refused with 401 unauthorized and nothing is stored.

Schema StripeWebhookBody. The Stripe event, unchanged.

Type: object.

Schema StripeWebhookReceipt.

Field Type Required Description
received enum: true yes The event was verified and recorded.
duplicate boolean yes True when this Stripe event id was already recorded; nothing changed.

Errors are RFC 9457 problem details and are never billed. Branch on type. See Errors.

Status Meaning
400 Invalid request: malformed JSON, an unknown field, a failed constraint or a size limit (invalid_request).
401 Missing, malformed, revoked or expired API key, or a bad webhook signature (unauthorized).
404 The resource does not exist in this organization (not_found).
429 Rate limited (rate_limited). Wait Retry-After seconds.
500 Unexpected server error (internal). Not billed; retry with the same Idempotency-Key.

Open the interactive reference to send this request from the browser. The full document is at /openapi.json.