Guides
Webhooks
Webhooks are HTTP POSTs TagPay sends to your server when something happens — a charge succeeds, a refund fails, a subscription renews. They're the source of truth for asynchronous events.
What webhooks are
A webhook is an outbound HTTP POST from TagPay to a URL you control. The body is JSON; the content type is application/json. Every delivery is signed so you can verify it came from TagPay.
See the full list in the event catalog.
Configure
Add a webhook URL in Dashboard → Developers → Webhooks. Pick the events you care about (or select All events to receive everything). Each endpoint gets its own signing secret used to verify incoming deliveries — see Verify signatures.
Delivery
TagPay sends the event as a JSON POST. The signature is in the x-tagpay-signature header — HMAC-SHA512 of the raw body, hex-encoded.
Respond with a 2xx status code within 15 seconds. Anything slower or non-2xx is treated as a failure and queued for retry.
Retries
Failed deliveries are retried with exponential backoff over roughly a week. Once all retries are exhausted the event is dropped permanently and surfaced in the dashboard's webhook log.
| Attempt | Delay after previous |
|---|---|
| #1 | 1 minute |
| #2 | 5 minutes |
| #3 | 30 minutes |
| #4 | 2 hours |
| #5 | 8 hours |
| #6 | 24 hours |
| #7 | 48 hours |
| #8 | 72 hours |
Idempotency
The same event may be delivered more than once — at-least-once delivery is the contract, exactly-once is not. Network blips, retries crossing in flight, and operational re-deliveries all cause duplicates.
Dedupe on the natural business key inside the payload, combined with the event name (also in the x-tagpay-event header). Charge and refund events share the same payload shape, so that's data.reference — note a refund payload describes the original charge, not the refund itself, so it won't disambiguate multiple refunds against one transaction. For transfers it's data.id (the transfer's own id). Subscriptions and invoices don't carry an id field at all — use data.subscriptionCode and data.invoiceCode respectively. Persist the key the first time you see it. A unique constraint on (event_name, business_key) is enough for most teams.