Register Your Endpoint
1
Register your webhook URL
Tell Quidkey where to deliver events.
2
Generate a signing secret
Generate the secret used to sign every event.The response returns the secret:
3
Revoke the secret (if needed)
Roll or revoke the secret during incident response. Generate a new one afterwards.
Named Endpoints & Per-Payment Routing
The single webhook URL above is the default destination for every payment. If you create payments through the embedded integration, you can also register multiple named endpoints in the Console (Settings → Webhooks) and route each payment to one or more of them.- Each endpoint has its own URL, its own signing secret, and an optional test/live scope (so a test-only receiver never sees live traffic).
- An embedded payment names endpoints in its
webhook_endpointsfield, up to 10. Its webhook is fanned out to every named endpoint, each signed with that endpoint’s own secret. This is built for platforms that send on behalf of several downstream businesses, each verifying with its own secret. - When a payment names no endpoint, Quidkey resolves the destination in order: the default
webhook_urlabove, then the merchant’s default endpoint, then the single active endpoint.
Named endpoints and per-payment routing apply to embedded payments only. Hosted-checkout and redirect payments always deliver to the single default
webhook_url registered above. See Named Webhook Endpoints for the full guide.Event Payload
Quidkey sends a Stripe-styleEvent envelope, so existing Stripe tooling can be reused. The payment object lives at data.object.
On the wire,
amount is a string holding a stringified integer of minor units ("1999" = £19.99), and the fees numeric fields (total_fees, each fee’s amount, rate_value) are strings too. Parse them before doing arithmetic.Verify Every Event
Quidkey signs each delivery with an HMAC. You must verify the signature before trusting or parsing the payload, otherwise anyone who discovers your URL could forge events.Signature Headers
Each request includes these headers:Stripe-Signature and X-Signature carry the identical value. Use Stripe-Signature with the Stripe SDK, or either header with a custom verifier.How the Signature Is Computed
The signature is an HMAC SHA-256 over the string`${timestamp}.${rawBody}` (the timestamp, a literal dot, then the raw request body), keyed by your webhook signing secret.
Verification Code
- Stripe SDK (Node.js)
- Plain Node.js (HMAC)
- Plain Python (HMAC)
Quidkey’s envelope and signature scheme are Stripe-compatible, so the Stripe SDK verifies them directly. Pass the raw body and the
Stripe-Signature header, keyed by your full whsec_... secret.Event Catalog
The
failed and reversed events carry the same envelope, without a fees object:
failed
reversed
Terminal vs transitional states.
pending is transitional and resolves to one of succeeded, failed, or canceled. succeeded is otherwise terminal, but a settled payment can still move to reversed later if it is refunded or reversed. A reversal is identified by the event type (quidkey.payment_request.reversed); there is no distinct reversed status value, so data.object.status stays the underlying transaction status (e.g. completed).Handling Events
Delivery and De-duplication
Quidkey makes a single delivery attempt per event. There is no automatic retry. If your endpoint is down or returns a non-2xx, the event is not retried automatically; you resend it manually.
Manual resend. You can resend a delivery from the Quidkey Console. A resend reuses the same
event.id and is re-signed with a fresh timestamp (so the signature and timestamp tolerance still validate).Reconciling Missed Events
If you suspect a webhook was missed (endpoint downtime, etc.), reconcile by querying the payment’s current status directly. This protected merchant endpoint returns the authoritative state:Best Practices
Verify before you parse
Verify before you parse
Capture the raw body, verify the signature against it (full secret, including
whsec_), and only then parse the JSON. Reject anything that fails with a 400.De-duplicate on event.id
De-duplicate on event.id
Persist every processed
event.id. Resends reuse the ID, so this is what keeps fulfilment exactly-once.Respond fast, work async
Respond fast, work async
Return
200 quickly and do heavy work (fulfilment, emails) asynchronously. A slow handler can time out the delivery.Enforce a timestamp tolerance
Enforce a timestamp tolerance
Reject events whose
X-Timestamp is outside ~300 seconds of now to defend against replay attacks.Next Steps
Testing
Trigger and verify test webhooks
Errors
Status codes and the error envelope
Amounts & Currencies
How amounts appear in payloads
Accept a Payment (Embedded)
Build the payment flow that triggers these events