REST API
Signed Webhook + Capturing Inbox
RequestValidateHard
What you will learn
- Verify an HMAC-SHA256 signature over the RAW request body, never a re-serialized object
- Use a capturing inbox to assert webhook delivery + signature in CI without a public receiver
- Reject stale timestamps to defend against replay — signature validity alone is not enough
Description
Verify an HMAC-SHA256 webhook signature over the raw body, with a built-in inbox so you can test it in CI without ngrok.
Test Scenario
import crypto from 'node:crypto';
const subId = 'ci-1';
await request.post('/api/v1/webhooks/signed', { data: { subId, event: 'order.created', payload: { id: 7 } } });
const inbox = await (await request.get('/api/v1/webhooks/signed?subId=' + subId)).json();
const d = inbox.deliveries[0];
const expected = 'sha256=' + crypto.createHmac('sha256', 'whsec_practice_signed_demo_secret')
.update(d['X-Timestamp'] + '.' + d.rawBody)
.digest('hex');
expect(d['X-Signature']).toBe(expected);
expect(d.fresh).toBe(true); // reject if false (replay window exceeded)Endpoint
/api/v1/webhooks/signedTesting Tips
- •POST to capture a delivery, then GET ?subId= to read its rawBody, X-Signature and X-Timestamp
- •Recompute HMAC-SHA256 over the literal `${X-Timestamp}.${rawBody}` with the published secret and compare
- •Sign/verify the RAW body bytes (never a re-serialized object) and reject stale timestamps to stop replay
// Playwright API Testing - RESTimport { test, expect } from '@playwright/test';test('REST API request', async ({ request }) => {const response = await request.get('/api/v1/resource');expect(response.ok()).toBeTruthy();expect(response.status()).toBe(200);const data = await response.json();expect(data).toBeDefined();});
Challenge ID: rest-webhook-hmac
2 frameworks available
API Playground
Send requests and inspect responses
Endpoint: /api/v1/webhooks/signed
Press Enter to send