Delivery guide

Receive events reliably

Validate every webhook, acknowledge it quickly, and process repeated deliveries without creating duplicate work.

Receive a webhook

Relayline sends the event as a JSON POST request. Your endpoint must use HTTPS and return a 2xx response within 10 seconds.

Webhook bodyjson
{
  "id": "evt_01J6Y5T4M8N2K7P9Q3R1",
  "type": "order.created",
  "delivery_id": "dlv_01J6Y3G8V9K2M4P7Q1R5",
  "created_at": "2026-09-04T14:32:18Z",
  "data": {
    "order_id": "ord_7M2L9",
    "total": 4800,
    "currency": "USD"
  }
}
HeaderDescription
Content-Typeapplication/json
Relayline-SignatureSigning timestamp and HMAC signature in the form t=timestamp,v1=signature.
Relayline-Delivery-IdThe delivery identifier used to trace the request.
Relayline-AttemptThe attempt number, beginning at 1.

Use the event id as an idempotency key in your own application. A retried delivery keeps the same event and delivery identifiers.

Verify the signature

Each request includes a Unix timestamp in seconds and a hexadecimal HMAC-SHA256 signature in Relayline-Signature. The signed message is the timestamp, a period, and the original request-body bytes. Pass those bytes as a Buffer to the verifier below, using the server-side webhook signing secret. Parse and validate JSON only after verification.

Signature verificationNode.js
import { createHmac, timingSafeEqual } from 'node:crypto';

/** Verify raw bytes before parsing JSON. A timestamp check does not replace deduplication. */
export function verifyRelaylineSignature(rawBody, header, secret, now = Date.now()) {
  if (!Buffer.isBuffer(rawBody) || typeof header !== 'string' || typeof secret !== 'string' || !secret) return false;
  if (!Number.isFinite(now)) return false;
  const match = /^t=(0|[1-9]\d*),v1=([a-f0-9]{64})$/i.exec(header);
  if (!match) return false;
  const timestamp = Number(match[1]);
  if (!Number.isSafeInteger(timestamp) || Math.abs(Math.floor(now / 1000) - timestamp) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${match[1]}.`).update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}

Acknowledge before processing

Return a successful response after validating and storing the event. Perform slower work asynchronously so the request does not exceed the 10-second timeout.

Recommended response

Return 204 No Content as soon as the event is safely queued in your application.

Retry schedule

Relayline retries when the destination times out, cannot be reached, or returns a final HTTP status outside 200–299.

AttemptApproximate delay after previous failureElapsed time
1Immediate0 minutes
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hoursApproximately 2 hours 36 minutes

Elapsed times are approximate and exclude time spent making requests and waiting for timeouts.

After the fifth unsuccessful attempt, the delivery changes to failed. Retrieve the delivery to inspect its final status.

For a complete example, follow one delivery through acceptance, a destination failure, and recovery.

Production handler checklist

  • Verify the signature before trusting the body.
  • Reject malformed requests with a 4xx response.
  • Store incoming events durably with atomic deduplication, and make worker side effects idempotent.
  • Queue expensive work before acknowledging the request.
  • Log the delivery ID so support investigations can trace an event.