Webhooks overview
Webhooks let PayDai push events to your application the moment they happen - a sale is paid, a commission is created, a member is activated - instead of you polling our API. PayDai sends an HTTP POST with a JSON body to an endpoint URL you control.
Events are dispatched using a transactional-outbox pattern, which guarantees at-least-once delivery. That means an event may arrive more than once. Your consumer must be idempotent: de-duplicate on event_id and treat repeated deliveries as no-ops.
The envelope
Every webhook shares the same outer envelope. The entity that changed lives under data.object; the envelope around it tells you what happened and in which workspace.
Webhook envelope
| Field | Type | Description |
|---|---|---|
event_id | string | Unique event identifier, prefixed evt_. Use this for de-duplication. |
event_type | string | The event name, e.g. sale.paid. See Event types. |
api_version | string | The payload schema version, currently 2026-06-01. |
created_at | datetime | ISO 8601 timestamp of when the event was emitted. |
workspace_id | string | The workspace the event belongs to, prefixed ws_. |
livemode | boolean | true for live data, false for test data. |
data.object | object | The full entity payload. Shape depends on event_type. |
The data.object shape is documented per entity under Payload reference. Treat the JSON examples there as ground truth.
Subscribe
Register a webhook endpoint from the Developer page at app.itspaydai.com/developer - enable Developer mode, then in the Webhook Endpoints section click Add Webhook and fill in the Add Webhook Endpoint form. Alternatively, create one over the API with POST /api/v1/webhook_endpoints/. When you create an endpoint you provide:
url- the HTTPS endpoint that should receive events. PayDai willPOSTevery selected event for your workspace to this URL.description- a label to identify the endpoint.enabled_events- the event types you want delivered to this endpoint. Pass a list of event names, or"*"to subscribe to all events.
Each endpoint also carries:
status- one ofactive,disabled, orpaused. Change it at any time to control whether the endpoint receives events.api_version- the payload schema the endpoint is pinned to. Defaults to2026-06-01.custom_headers- an optional map of extra headers PayDai sends with every delivery (for example a static auth token your receiver checks). Some headers are reserved - see Custom headers.
The full list of valid event types is available from GET /api/v1/webhook_endpoints/supported-events/ (see also Event types).
For the full REST API - creating, listing, updating, deleting endpoints and revealing the signing secret - see Managing webhook endpoints.
PayDai auto-disables an endpoint after repeated consecutive delivery failures. If your endpoint flips to disabled, fix the receiver and re-enable it from the dashboard or API.
Receiver contract
Your endpoint should:
- Respond
2xxquickly. Acknowledge receipt with any2xxstatus as soon as you have safely persisted the event. Respond within ~10 seconds or the delivery is treated as failed. - Do work asynchronously. Don't run slow business logic before responding. Persist the raw event, return
2xx, then process it in a background worker. - Expect retries. Any non-
2xxresponse (or a timeout) is treated as a failure and the event is retried. Combined with at-least-once delivery, this is why idempotency is required.
Delivery & retries
- At-least-once. Every event is delivered at least once, and may be delivered more than once.
- Respond fast. Return any
2xxwithin ~10 seconds. A5xx, any other non-2xx, or a timeout counts as a failed delivery. - Automatic retries. Failed deliveries are retried with exponential backoff by PayDai's delivery queue - you don't request a retry, it happens for you.
- De-duplicate on
event_id. Retries reuse the sameevent_id. Skip anyevent_idyou've already processed so a retried delivery is a no-op.
Verify authenticity
When you register an endpoint, PayDai returns a signing secret prefixed whsec_. The full secret is returned in the creation response - copy it and store it securely (for example, in your secrets manager). If you don't save it then, you can retrieve it again at any time with Reveal secret; everywhere else the API returns only a masked secret_hint. This secret is what your endpoint uses to verify that an inbound request genuinely came from PayDai.
Delivery headers
Every delivery includes:
| Header | Description |
|---|---|
Paydai-Signature | t=<unix_timestamp>,v1=<hex_hmac> - the timestamp and signature. |
Paydai-Event-Type | The event name, e.g. sale.created. |
Paydai-Api-Version | The payload schema version, e.g. 2026-06-01. |
Paydai-Event-Id | The event id, matching event_id in the body. |
The signature
The signature is an HMAC-SHA256:
- Key = your endpoint signing secret (
whsec_...). - Message = the string
"{t}." + <raw request body>, wheretis the timestamp from the header and the body is the exact raw bytes you received. Do not re-serialize the JSON - any reformatting will change the bytes and break the signature.
To verify a delivery:
- Parse
tandv1from thePaydai-Signatureheader. - Recompute the HMAC-SHA256 over
t + "." + rawBodyusing your signing secret. - Compare your computed hex digest against
v1in constant time. Reject the request if they don't match. - Reject the request if
tis too old - recommend a tolerance of 5 minutes - to prevent replay attacks.
Replay protection is the consumer's responsibility: reject stale timestamps yourself (a ~5 minute tolerance works well). Always compare signatures in constant time (never with ==) to avoid timing attacks.
Verify in Node.js
Verify in Python
This differs from request authentication. When you call the PayDai API you send your API key as a Bearer token (see Authentication). Webhook authenticity is the reverse problem - proving an inbound request really came from PayDai.
Idempotency & ordering
- De-duplicate on
event_id. Store processedevent_ids and skip any you've already handled. Because delivery is at-least-once, the same event can arrive multiple times. - Don't assume ordering. Events are not guaranteed to arrive in the order they occurred. For example,
sale.paidcould reach you beforesale.created. Reconcile state using the entity's own fields (such asstatusand timestamps) rather than relying on arrival order.
livemode
The livemode flag distinguishes test from live traffic:
livemode: false- the event was generated by test data. Use this to drive your sandbox and integration tests.livemode: true- the event reflects real workspace activity.
Branch on livemode so test events never touch production records.
Versioning
api_version (currently 2026-06-01) identifies the schema of the payload under data.object. When PayDai makes a backwards-incompatible change to a payload, it ships under a new api_version. Pin your consumer to the version you built against and check api_version on each event so you can detect and handle upgrades deliberately.
