POST with a JSON body to the URL you register.
For the list of events and their payloads, see Webhook Events.
Register your webhook
Register your endpoint withPOST /v1/api/webhook. This is the only way to register a webhook. Payment and other API requests don’t accept a callback URL.
- One webhook per merchant account. Calling
POST /v1/api/webhookagain replaces your existing URL, description, event list and headers. - Subscribe to each event by name. You only receive events listed in
eventTypes. Event names are case-sensitive and lowercase (for exampletransaction_completed). Names are not checked when you subscribe, so a misspelt name is accepted but never delivered. UseGET /v1/api/webhook/eventsto get the exact names. - Use a public HTTPS URL. The URL must use
https, and must not point tolocalhostor a private, loopback or link-local address.
GET /v1/api/webhook. To stop all deliveries, call DELETE /v1/api/webhook/{id}.
Webhook structure
Every webhook has the same envelope:
Example (
data shortened):
Content-Type: application/json and Accept: application/json, plus any custom headers you registered. Payloads may gain new fields over time; ignore fields you don’t recognise.
Responding to webhooks
- Return any
2xxstatus to acknowledge the webhook. Any other status, a redirect, a connection error or no response within 15 seconds counts as a failed delivery. - Respond quickly. Acknowledge first, then process the event asynchronously (for example, by putting it on a queue).
- Redirects are not followed. Register the final URL, not one that redirects.
No automatic retries
Tender sends each webhook once. Failed deliveries are not retried. Every delivery attempt, successful or failed, is recorded in your webhook logs. Use webhooks as a fast signal, and don’t rely on them alone:- Check the webhook logs for failed deliveries.
- For payments, confirm the state with validate payment when a webhook is missing or you need certainty.
- Reconcile periodically against the API.
Deduplicating events
You can receive more than one webhook for the same thing. For example,transaction_partially_paid can arrive several times while a payment stays partially paid, and sub_user_wallet_transaction_detected arrives again each time a deposit’s confirmation count changes.
eventId is new on every delivery, so it can’t be used to detect duplicates. Deduplicate on the business identifiers instead:
Make your handler idempotent: processing the same event twice should have no extra effect.
Securing your endpoint
Tender webhooks are not signed. There is no signature header to verify. Instead, authenticate deliveries with the custom headers you register: Tender sends them, unchanged, with every webhook.Setting custom headers
Register a secret value inheaders when you create the webhook:
signedHeaders. A few headers can’t be overridden and are dropped if you set them: Host, Content-Length, Connection, Transfer-Encoding, Upgrade, Proxy-Authorization, X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto.
Verifying custom headers
On your endpoint, reject any request that doesn’t carry the expected value:Best practices
- Use a long, randomly generated secret value, and compare it in constant time.
- Keep the value out of source control, and rotate it by calling
POST /v1/api/webhookwith a new value. - Treat the payload as a notification. Before you ship goods or credit a customer, confirm the payment with validate payment.
- Serve the endpoint over HTTPS only.
What doesn’t send webhooks
There are no webhooks for payouts, conversions or on-ramp orders created through the API. Poll theirGET endpoints to track status, within the rate limits:
- Payouts:
GET /v1/api/payout/{id} - Conversions:
GET /v1/api/conversions/{id} - On-ramp:
GET /v1/api/onramp/{reference}