Register a webhook
Register your URL and the event types it subscribes to withPOST /webhooks; the signing secret comes back once, in this response only, so store it immediately. Webhooks are managed with an API key or a user session; an agent key cannot manage them.
signingSecret:
A new webhook is ENABLED; it becomes DISABLED when you set that status or when it fails five events in a row.
url must be HTTPS and publicly reachable. enabledEvents needs at least one entry: any event type from the Event catalog, or the wildcard "*" (which must be the only entry). sources defaults to ["own"]; add "connected" to also receive events a customer connection authorizes. Omitting "connected" never opts a webhook into customer events, and the event type must still match enabledEvents. description (up to 100 characters) and tags are optional.
Verify a signature
Every delivery is a POST whose JSON body is the event, withtype naming what happened and the resource snapshot at data.object, and three lowercase headers carry the signature:
Verify before trusting a payload. Use the official
standardwebhooks library (Python and Node); construct it with your whsec_ secret and pass the raw body plus headers. verify returns the parsed event, or raises on a bad signature:
Verify against the raw request body, the exact bytes Natural sent. Parsing and re-serializing the
JSON first changes the bytes and breaks verification.
webhook-timestamp is outside a tolerance window, and it accepts a delivery if any of the space-separated signatures verifies. That second rule is what lets a rotation with POST /webhooks/{webhookId}/rotate-secret overlap two valid secrets: during the grace period you set with expiresInSeconds (0 to 86400 seconds), each delivery carries one signature per active secret, newest first.
Verify manually, without the library
Verify manually, without the library
The signed content is the string
{webhook-id}.{webhook-timestamp}.{body}. Strip the whsec_
prefix from the secret, base64-decode the remainder for the HMAC key, compute HMAC-SHA256, and
base64-encode the result. Compare it against each space-separated v1,<sig> entry.Handle retries and replay
Treat every delivery as a possible duplicate, keyed on the event ID, and send a stored event again withPOST /webhooks/{webhookId}/events/{eventId}/redeliver when your handler missed it.
Natural treats any 2xx response as success and gives up on a single request after 30 seconds. A failed delivery (non-2xx, network error, or timeout) is retried up to 7 attempts in total, with delays of 5 seconds, 5 minutes, 30 minutes, 2 hours, 8 hours, and 12 hours plus up to 20% jitter. Every retry reuses the same webhook-id, so record the event IDs you have processed and skip any you have already seen. A webhook that fails every attempt for five consecutive events becomes DISABLED; set it back to ENABLED with PATCH /webhooks/{webhookId} once your handler is healthy. A connected delivery is authorized against the customer connection immediately before each send; if that check is temporarily unavailable, the delivery is deferred and re-checked about every 5 minutes for up to 24 hours without consuming a retry attempt.
The webhook-id header is the event ID. GET /events lists the same events on demand and GET /events/{eventId} reads one, which is how you fill a gap after an outage. For a connected customer, pass partyId and eventType: history covers connected events created since the connection began plus any events actually delivered to one of your webhooks, and after revocation only the delivered records remain.
PENDING, moves to DELIVERING while the request is in flight, and ends DELIVERED or FAILED; it is UNKNOWN when no response was recorded and CANCELED when the authorization check before sending no longer passes.
You can redeliver an event for 90 days after it was created, to a webhook that was one of its original destinations. The webhook must be ENABLED and must still accept the event’s source: removing "connected" from sources closes redelivery of its connected events (webhook_source_disabled). One manual redelivery per event and webhook runs at a time, and you can request up to 10 per webhook per minute; beyond that the call returns redelivery_rate_limited. A redelivery keeps the original payload and webhook-id but uses the webhook’s current URL and active signing secret with a fresh webhook-timestamp and signature, and it can overlap with automatic retries, so handle it as a duplicate. A manual failure does not count toward automatic disabling.
For a delegated event, the delegation that authorized the original delivery must still be active and grant access to that event type. Revoking it permanently closes redelivery for that event and webhook; a new delegation does not reopen it. A delegated redelivery goes only to the webhook that received the original delivery, never to the customer’s webhooks.
Connected copies carry the same payload as the owner’s copy, with no connection metadata added and no fields removed. Which events reach a webhook with "connected" in sources:
Payment milestones can produce separate sender and recipient events.
sourcePartyId identifies the party whose view the event represents, and the recipient view omits sender-only details. A webhook connected to both parties can receive both views with different event IDs, so deduplicate on the event ID, not the payment ID.