Private beta · Pilot access only. Live polling is not enabled yet. Paid checkout is not enabled.

Verified, signed webhook delivery

Verify ownership

A destination must be a public HTTPS URL on port 443. Verification POSTs {"type":"endpoint.verification","challenge":"UNPREDICTABLE_NONCE"}. Return a 2xx application/json response containing the identical challenge within five seconds. Challenges expire after five minutes and are single use. Redirects are not followed. Save the signing secret returned when creating a destination.

Verify signatures before parsing

Delivery sends the exact retained event bytes. Headers: RerunLab-Event-Id, RerunLab-Delivery-Id, RerunLab-Timestamp (Unix seconds), and RerunLab-Signature (v1=hex). Compute HMAC-SHA256(secret, timestamp + "." + rawBody), compare in constant time, and reject timestamps older than five minutes or implausibly in the future. Preserve the raw body; reserializing JSON changes the signature.

import crypto from 'node:crypto';
export function verify(rawBody, headers, secret) {
  const timestamp = headers['rerunlab-timestamp'];
  if (!/^\d+$/.test(timestamp ?? '')) return false;
  if (Math.abs(Date.now()/1000 - Number(timestamp)) > 300) return false;
  const expected = Buffer.from('v1=' + crypto.createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest('hex'));
  const supplied = Buffer.from(headers['rerunlab-signature'] ?? '');
  return supplied.length === expected.length && crypto.timingSafeEqual(supplied, expected);
}

Acknowledge and deduplicate

Durably accept an event, then return 2xx quickly. Delivery is at least once: keep a unique constraint on event.id and acknowledge duplicates without repeating effects. An acknowledgement proves the receiver accepted the HTTP request; it does not prove a downstream database or business workflow completed.

Retries and replay

Up to eight attempts within 24 hours, scheduled around 0, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 12 hours and 23 hours from creation. Positive jitter avoids bursts. HTTP 408/425/429, 5xx and network failures retry. Other 4xx and redirects fail permanently. Retry-After is never shortened; a retry beyond the deadline fails. The overall request deadline is ten seconds and receiver response bodies are capped at 8 KiB.

Replay creates a new delivery ID with the original event bytes and event ID. It uses the original immutable destination URL revision. Editing a URL creates a new revision that must be verified for future events; already queued deliveries remain pinned. Deleting a destination cancels pending deliveries. Secret rotation has a 24-hour previous-secret overlap for receivers; keep both during that period.

Python verification example

import hashlib, hmac, re, time

def verify(raw_body, headers, secrets):
    stamp = headers.get('rerunlab-timestamp', '')
    signature = headers.get('rerunlab-signature', '')
    if not re.fullmatch(r'[0-9]+', stamp):
        return False
    if abs(time.time() - int(stamp)) > 300:
        return False
    message = stamp.encode('ascii') + b'.' + raw_body
    return any(hmac.compare_digest(
        'v1=' + hmac.new(secret.encode(), message, hashlib.sha256).hexdigest(),
        signature) for secret in secrets)

Keep the current and previous secret during rotation. Executable Node and Python helpers are in examples/polling and are tested against the same signed bytes, stale timestamps and modified bodies. Verification alone does not deduplicate: save the event ID with a unique constraint in the same transaction as enqueueing your work.