Webhooks overview¶
How webhook delivery works end-to-end. The full per-event payload schemas are in Event catalog; the signature verification recipe is in Verifying signatures. This page is the conceptual layer that ties them together.
Why webhooks (vs polling)¶
Polling burns rate-limit budget. A naive sync that polls /events/{id}/participants every minute consumes 60 of your 500 hourly budget per hour. Add /program, /activities, /locations, and you're past 200/h before any actual data arrives.
Webhooks consume zero rate-limit budget at idle. Your integration listens, reacts to the snapshot in the payload directly, and only calls the API for related data not in the snapshot (e.g. parent event metadata for an application.approved payload). Same overall data freshness, hundredth of the API load.
If you're polling, you're probably doing it wrong. Almost every "I want to know when X happens" use case has a corresponding webhook event.
Payload pattern: include data¶
Webhook payloads carry the full resource snapshot at the moment the event occurred. You can act directly on the payload without an API round-trip:
{
"type": "application.approved",
"event_id": "evt_abc123",
"data": {
"id": "app_xyz",
"participant_id": "ptp_001",
"user_id": "usr_def456",
"status": "approved",
"approved_at": "2026-05-02T09:14:00Z",
"approver_id": "usr_org_123",
"custom_form_fields": { /* ... */ }
}
}
Saves a round-trip per event, reduces rate-limit pressure, and makes your handler self-contained.
If you need related data not in the payload (e.g. you received application.approved and want the full event metadata too), call the Read API with your installation token.
The one exception is data.deletion_required - its payload is minimal because the only action is "delete," not "process."
Subscribing¶
You don't manage subscription objects at runtime. Your integration manifest declares:
webhook_url- single HTTPS endpoint where every delivery POSTsdeclared_webhook_event_types- the array of event types you want to receive
When a token is issued (organizer connects your integration to an event), deliveries for the declared event types flow to your URL automatically. No per-installation subscription step required.
At registration you receive:
- An HMAC signing secret - generated by Revento, shown to you once. Afterwards we store only the hash. If you lose it, rotate.
To change which event types you receive, edit your integration manifest. To pause deliveries entirely, clear declared_webhook_event_types in the manifest; to resume, re-add.
Signed delivery¶
Every delivery has these headers:
Content-Type: application/json
User-Agent: Revento-Webhooks/1.0
X-Revento-Event: application.approved
X-Revento-Delivery-Id: 3f9b1e2a-7e8f-4d5c-bc0a-1f2e3d4c5b6a
X-Revento-Timestamp: 1747000123
X-Revento-Signature: sha256=8e1c4b...
The X-Revento-Signature is HMAC-SHA256 of {timestamp}.{raw_body} using your integration's webhook signing secret. Verify this on every delivery before trusting the body. See Verifying signatures.
The X-Revento-Delivery-Id is unique per delivery - but retries reuse it. Use it as a dedup key.
The 5-minute replay window is your responsibility: reject deliveries where |now - timestamp| > 300 seconds.
The 10-second timeout¶
Each delivery attempt has 10 seconds to receive a 2xx response. If your handler takes longer, Revento closes the connection and counts the delivery as failed for retry purposes.
Implication: respond quickly, queue real work async on your side.
@app.route("/webhooks/revento", methods=["POST"])
def webhook():
verify_signature() # cheap
payload = request.json
# Queue real work - don't block the response
queue.enqueue("process_webhook", payload, request.headers["X-Revento-Delivery-Id"])
return "ok", 200 # respond fast
Retry curve¶
If you respond non-2xx (or time out at 10s), we retry with this curve:
| Attempt | Delay after previous failure |
|---|---|
| 1 (initial) | - |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
| 7 | 24 hours |
| Dead-letter | After 7th failure |
After 6 retries (7 total attempts: initial + 6 retries) failing, the delivery moves to dead-letter. Dead-lettered deliveries can be replayed individually.
Retry timing is approximate. Don't assume exact delivery at the listed intervals.
When we don't retry¶
We retry on:
- 5xx responses
- 408 Request Timeout
- 429 Too Many Requests (rare from your end, but valid)
- Network errors / connection failures
- Our 10-second per-attempt timeout
We do not retry on:
- 4xx other than 408/429 - interpreted as "you're permanently unable to process this payload"
- Successful 2xx (obviously)
A 400/401/403/404 from your endpoint moves the delivery directly to dead-letter. Get your handler right or the dead-letter queue fills up.
No static outbound IP¶
Webhook deliveries originate from Revento Cloud's egress without a stable public IP. Don't try to allowlist us by IP - use signature verification (the only supported method).
If your network policy strictly requires IP-based allowlisting, you'll need a mutually-authenticated reverse proxy or a similar pattern. Revento doesn't promise a stable origin IP.
Dead-letter inspection and replay¶
Dead-lettered deliveries can be inspected and replayed individually.
Replay sends a fresh delivery with a new X-Revento-Delivery-Id. Your handler should treat it as a new attempt, not as a duplicate of the original.
Secret rotation¶
To rotate your integration's webhook signing secret:
See API → endpoints → developer and OAuth management endpoints for the full request/response shape.
This:
- Generates a new secret, returned once in the response.
- Keeps the old secret valid for 24 hours alongside the new one.
- During the overlap, deliveries are signed with both secrets - Revento sends
X-Revento-Signaturefor the new secret andX-Revento-Signature-Previousfor the old secret. Your verifier should accept either header during the overlap window. - After 24 hours, the old secret is invalidated.
The 24-hour overlap lets you roll the new secret to all your servers without a downtime window.
Rotation is the recommended response to a suspected secret leak - it's also a fine occasional hygiene practice.
Operational safeguards¶
Revento may temporarily pause webhook delivery during sustained endpoint failures. If that happens, remediate the endpoint and resume or replay deliveries using the available management API or tools.
If you need to dry-run your endpoint without waiting for a real event, use POST /developer/integrations/{id}/send-test-webhook. It sends one representative signed delivery out of band.
Changing which events you receive¶
To add or remove event types: edit your integration manifest's declared_webhook_event_types. Future deliveries reflect the updated set immediately. Past events are not replayed unless you explicitly request a replay.
Pause your manifest's webhook handling entirely by removing all entries from declared_webhook_event_types. While none are declared, no deliveries fire. Re-add later to resume.
What happens if we lose your endpoint¶
If your endpoint is unreachable for an extended period, deliveries pile up in dead-letter. Once you've fixed your endpoint, replay individual dead-letter deliveries or reconcile via API fetch.
For long outages (>24h), reconciling via API is more practical than replaying every dead-lettered delivery.