Outgoing Webhook DispatcherHMAC SHA-256 Signed

Outgoing Webhooks & Delivery Inspector

Test real webhook receivers and event-driven architectures without waiting for third-party service webhooks. Whenever a REST or GraphQL mutation occurs inside Playground API, the server dispatches a real HTTP POST request to your application or local ngrok tunnel with cryptographic HMAC SHA-256 signatures and provides an in-browser delivery inspector with retry simulation.

Interactive Webhook Inspector Studio

Register your receiver URL, fire test pings, trigger mutations, and inspect dispatched headers, payloads, and response codes.

Webhook Dispatcher & Inspector Studio0 Active Receivers

Live HMAC SHA-256 signed event delivery inspector and retry sandbox

Recent outgoing webhook deliveries. Click any row to inspect signed headers and JSON payloads.
No webhook deliveries recorded yet. Perform a mutation (e.g. POST /posts) or click "Send Test Ping" above.

Dispatched Header Specifications

Header NameExample ValueDescription
X-Playground-Signaturesha256=a8c5f...4e91HMAC-SHA256 signature computed over raw JSON body using your secret key.
X-Playground-Eventpost.createdName of the triggered event channel (e.g. post.created, auth.registered).
X-Playground-Deliverydel_89f1...2c90Unique delivery UUID for idempotency deduplication.
User-AgentPlayground-API-Webhook-Dispatcher/1.0Official dispatcher user-agent identifier.

Receiver Verification Recipes

1. Node.js / Express Webhook Receiver

server.js (Express)
1
import express from 'express';
2
import crypto from 'crypto';
3
4
const app = express();
5
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'whsec_demo_secret_key_123';
6
7
// Capture raw body for exact cryptographic HMAC verification
8
app.post('/api/webhooks/playground', express.raw({ type: 'application/json' }), (req, res) => {
9
const signature = req.headers['x-playground-signature']; // "sha256=<hash>"
10
const event = req.headers['x-playground-event'];
11
const rawBody = req.body.toString('utf8');
12
13
// Verify HMAC SHA-256 signature
14
const expectedSignature = 'sha256=' + crypto
15
.createHmac('sha256', WEBHOOK_SECRET)
16
.update(rawBody)
17
.digest('hex');
18
19
if (signature !== expectedSignature) {
20
return res.status(401).json({ error: 'Invalid HMAC signature' });
21
}
22
23
const payload = JSON.parse(rawBody);
24
console.log(Received Webhook [${event}]:, payload.data);
25
26
// Return 200 OK to acknowledge delivery
27
res.status(200).json({ received: true });
28
});
29
30
app.listen(3000, () => console.log('Webhook receiver running on port 3000'));

2. Next.js App Router Webhook Route Handler

app/api/webhooks/route.ts
1
// app/api/webhooks/route.ts (Next.js App Router)
2
import { NextRequest, NextResponse } from 'next/server';
3
import crypto from 'crypto';
4
5
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'whsec_demo_secret_key_123';
6
7
export async function POST(req: NextRequest) {
8
const rawBody = await req.text();
9
const signature = req.headers.get('x-playground-signature');
10
const event = req.headers.get('x-playground-event');
11
12
const expectedSignature = 'sha256=' + crypto
13
.createHmac('sha256', WEBHOOK_SECRET)
14
.update(rawBody)
15
.digest('hex');
16
17
if (signature !== expectedSignature) {
18
return NextResponse.json({ error: 'Signature mismatch' }, { status: 401 });
19
}
20
21
const data = JSON.parse(rawBody);
22
console.log(Processed ${event} event:, data);
23
24
return NextResponse.json({ received: true });
25
}

3. Python FastAPI Receiver

main.py (FastAPI)
1
from fastapi import FastAPI, Request, HTTPException, Header
2
import hmac
3
import hashlib
4
import json
5
6
app = FastAPI()
7
WEBHOOK_SECRET = "whsec_demo_secret_key_123"
8
9
@app.post("/api/webhooks/playground")
10
async def receive_webhook(request: Request, x_playground_signature: str = Header(None)):
11
raw_body = await request.body()
12
13
expected_sig = "sha256=" + hmac.new(
14
WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256
15
).hexdigest()
16
17
if not hmac.compare_digest(x_playground_signature or "", expected_sig):
18
raise HTTPException(status_code=401, detail="Invalid signature")
19
20
payload = json.loads(raw_body)
21
print(f"Received event {payload['event']}: {payload['data']}")
22
return {"status": "ok"}

Supported Event Catalog

post.*Posts Resource Events

  • • post.created — Triggered on POST /posts
  • • post.updated — Triggered on PUT / PATCH /posts/:id
  • • post.deleted — Triggered on DELETE /posts/:id

auth.*Authentication Events

  • • auth.registered — Triggered on POST /auth/register
  • • auth.login — Triggered on POST /auth/login

custom.*Custom Dynamic Collections

  • • custom.<collection>.created — POST /custom/:collection
  • • custom.<collection>.updated — PUT / PATCH /custom/:collection/:id
  • • custom.<collection>.deleted — DELETE /custom/:collection/:id

user.* / comment.*Users, Comments & Todos

  • • user.created / user.updated / user.deleted
  • • comment.created / comment.updated / comment.deleted
  • • todo.created / todo.updated / todo.deleted