End-to-end: webhook handling
1. Register an endpoint
Section titled “1. Register an endpoint”curl -X POST https://api.rieckflow.com/v1/webhook-endpoints \ -H "Authorization: Bearer $RIECK_API_KEY" \ -H "Idempotency-Key: webhook-production" \ -H "Content-Type: application/json" \ -d '{ "url": "https://erp.acme.example/webhooks/rieck", "description": "Production events", "eventTypes": ["invoice.paid", "customer.past_due"] }'The response includes data.secret once. Store the complete whsec_... value in your
secret manager before acknowledging setup.
2. Verify the raw request
Section titled “2. Verify the raw request”Every delivery carries webhook-id, webhook-timestamp and webhook-signature. Verify
the signature against the raw request body bytes before parsing JSON. The default
timestamp tolerance is five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyRieckWebhook( rawBody: string, headers: Headers, secret: string,): unknown { const id = headers.get("webhook-id"); const timestamp = headers.get("webhook-timestamp"); const candidates = (headers.get("webhook-signature") ?? "").split(" "); if (!id || !timestamp || candidates.length === 0) throw new Error("Missing webhook headers");
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)); if (!Number.isFinite(age) || age > 300) throw new Error("Stale webhook timestamp");
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = createHmac("sha256", key) .update(`${id}.${timestamp}.${rawBody}`, "utf8") .digest(); const valid = candidates.some((candidate) => { const value = Buffer.from(candidate.replace(/^v1,/, ""), "base64"); return value.length === expected.length && timingSafeEqual(value, expected); }); if (!valid) throw new Error("Invalid webhook signature"); return JSON.parse(rawBody);}During secret rotation the signature header can contain two space-separated signatures. Accept a match against either signature while the 24-hour overlap is active.
3. Acknowledge and process safely
Section titled “3. Acknowledge and process safely”- Verify the signature.
- Insert
webhook-idinto a unique/deduplication table. - Return any
2xxresponse quickly. - Process the event asynchronously.
- Fetch the referenced invoice, customer, subscription or case before changing state.
Rieck retries 408, 429 and 5xx responses with backoff. Other 4xx responses are
treated as permanent rejections. Redirects are never followed. Use GET /v1/events for
catch-up and the replay endpoint for failed deliveries.