Delivery and security
Delivery Payload
Webhook deliveries are sent as POST requests with a JSON body:
{
"id": "8fd6ce2f-8b55-4b2e-9f8c-5b7f6f0b16f1",
"event": "scan.created",
"timestamp": "2024-12-31T23:59:59.000Z",
"organisation_id": "507f1f77bcf86cd799439010",
"schema_version": "1",
"source": "admin",
"data": {
"scan": {
"id": "507f1f77bcf86cd799439015",
"company": "507f1f77bcf86cd799439010",
"source": "web",
"member": {
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"memberId": "MEM001"
},
"input": "ABCD1234",
"location": "Main Entrance",
"result": "success",
"reason": "Pass accepted",
"timestamp": "2024-12-31T23:59:59.000Z"
}
}
}
Delivery Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
User-Agent | RemyPass-Webhooks/1.0 |
X-RemyPass-Signature | HMAC-SHA256 signature of the raw request body |
X-RemyPass-Event | Event name, for example scan.created |
X-RemyPass-Delivery | Stable event ID, shared across endpoints, retries and replays |
X-RemyPass-Timestamp | Event occurrence time from the payload |
Custom headers configured on the webhook subscription are also included with each delivery. Reserved RemyPass headers and transport headers cannot be overridden. Redirects are not followed.
Resource money amounts are integer minor units with currency codes; percentage configuration values remain percentages. source is admin, member_portal, public_checkout, import, integration, or system. Test events add test: true. Event IDs are opaque strings; do not assume a UUID format.
Signature Verification
Use the subscription secret to verify the raw request body before processing the event.
const crypto = require("crypto");
function verifyRemyPassWebhook(rawBody, signature, secret) {
if (typeof signature !== "string" || !/^[a-f0-9]{64}$/i.test(signature)) return false;
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));
}
Retries and Timeouts
Your endpoint should return any 2xx status quickly after accepting the event. If the endpoint returns a non-2xx status or times out, RemyPass retries delivery with exponential backoff.
Defaults:
| Setting | Default | Limit |
|---|---|---|
| Delivery timeout | 30 seconds | 1 to 300 seconds |
| Retry delay | 60 seconds | 1 to 3600 seconds |
| Max retries | 3 | 0 to 10 |
Webhook subscriptions are disabled after 10 consecutive exhausted deliveries. Attempts are stored in MongoDB, processed by a background worker and resumed after restarts. An expired processing lease makes an interrupted delivery eligible for another attempt. Event fan-out is idempotent.
Delivery is at least once once the event has been persisted: deduplicate using id and do not rely on arrival order. A successful request can be repeated if the worker stops before saving its response. Replay preserves the event ID and payload. History and event deduplication records are retained for 90 days.
Application model writes emit events; writes made directly to MongoDB do not. When the application write uses a transaction, its outbox record uses the same transaction, so rolled-back changes do not send events. Non-transactional writes and their outbox writes are separate operations; a process or database failure between them can leave a committed change without a webhook.
See subscription management to send tests, inspect delivery history, and replay events.