verifyWebhook()
Signature
Section titled “Signature”async verifyWebhook<T extends WebhookEvent = WebhookEvent>( body: string | Buffer, headers: Record<string, string | string[] | undefined>, options?: { tolerance?: number },): Promise<VerifyResult<WebhookVerifyData<T>>>Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
body | string | Buffer | Raw request body (must not be parsed) |
headers | Record<string, string | string[] | undefined> | Raw request headers |
options.tolerance | number | Override timestamp tolerance in seconds (default: 300, max: 600) |
Return type
Section titled “Return type”interface WebhookVerifyData<T> { deliveryId: string; // From X-Rakomi-Delivery-Id header (falls back to webhook-id if absent) timestamp: number; // Unix timestamp from webhook-timestamp header payload: T; // Parsed and verified webhook event}const result = await ca.verifyWebhook(rawBody, headers);
if (result.ok) { const { deliveryId, timestamp, payload } = result.data; console.log(`Event: ${payload.type}, Delivery: ${deliveryId}`);
switch (payload.type) { case 'user.created': // Handle new user break; case 'user.deleted': // Handle user deletion break; }} else { console.error('Webhook invalid:', result.error.code);}Required headers
Section titled “Required headers”Rakomi sends these headers with every webhook delivery, following the Standard Webhooks specification:
| Header | Description |
|---|---|
webhook-id | Stable event ID (same across retries) |
webhook-signature | HMAC-SHA256 signature (v1,<base64>) |
webhook-timestamp | Unix timestamp (seconds) for replay prevention |
X-Rakomi-Delivery-Id | Unique per-attempt delivery ID |
X-Rakomi-Event | Event type for header-based routing |
X-Rakomi-Attempt | Attempt number (1-based) |
Signed content format
Section titled “Signed content format”The signature is computed over: {webhook-id}.{webhook-timestamp}.{body}
During signing key rotation (24h overlap), the webhook-signature header contains two signatures separated by a space: v1,<new_sig> v1,<old_sig>. The SDK tries each and succeeds if any matches.
Framework examples
Section titled “Framework examples”Express
Section titled “Express”import express from 'express';
const app = express();
// Use express.raw() for webhook routes — NOT express.json()app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const result = await ca.verifyWebhook(req.body, req.headers); if (!result.ok) { return res.status(400).json({ error: result.error.code }); } // Handle event... res.sendStatus(200);});Next.js App Router
Section titled “Next.js App Router”export async function POST(request: Request) { const body = await request.text(); // Get raw body as string const headers = Object.fromEntries(request.headers);
const result = await ca.verifyWebhook(body, headers); if (!result.ok) { return Response.json({ error: result.error.code }, { status: 400 }); } // Handle event... return Response.json({ received: true });}app.post('/webhook', async (c) => { const body = await c.req.text(); // Get raw body as string const headers = Object.fromEntries(c.req.raw.headers);
const result = await ca.verifyWebhook(body, headers); if (!result.ok) { return c.json({ error: result.error.code }, 400); } // Handle event... return c.json({ received: true });});Tolerance
Section titled “Tolerance”The timestamp tolerance (default: 300 seconds / 5 minutes) protects against replay attacks. Timestamps that are too old or too far in the future are rejected.
// Override per-callconst result = await ca.verifyWebhook(body, headers, { tolerance: 60, // Only accept webhooks from the last 60 seconds});Maximum tolerance is 600 seconds (10 minutes).
WebhookEvent
Section titled “WebhookEvent”interface WebhookEvent { id: string; // Delivery ID type: string; // Event type (e.g., 'user.created') timestamp: string; // ISO 8601 timestamp tenantId: string; // Tenant ID userId?: string; // User ID (optional for system events) severity: 'critical' | 'warning' | 'info'; data: Record<string, unknown>; // Event-specific data meta: { api_version: string; event_language?: string; tenant_country?: string; user_country?: string; };}Configuration
Section titled “Configuration”The webhookSecret must be set in the Rakomi constructor:
const ca = new RakomiClient({ apiKey: 'akm_live_xxx', webhookSecret: 'rksec_xxx', // From your Rakomi dashboard});If webhookSecret is not configured, verifyWebhook() returns { ok: false, error } with code config/missing_webhook_secret.
Error codes
Section titled “Error codes”| Code | When |
|---|---|
webhook/timestamp_too_old | Timestamp exceeds tolerance window (too far in the past) |
webhook/timestamp_too_new | Timestamp is too far in the future |
webhook/invalid_signature | HMAC signature doesn’t match. Are you passing the raw request body? |
webhook/invalid_secret | Webhook secret is corrupted (invalid key length after decode) |
webhook/missing_header | Required headers (webhook-id, webhook-signature, webhook-timestamp) are missing |
webhook/invalid_body | Body is not valid JSON |
config/missing_webhook_secret | webhookSecret not configured |
See Error Codes for full details.