Docs
Getting started
Six steps from sign-up to verifying signatures on the receiving side.
1. Sign up
Sign up at https://app.webhookadmin.com/signup with your email address, Google or GitHub. With email, we send you a sign-in link that is valid for 15 minutes.
On your first sign-in, create an organization (your company or team). Plans, billing and members belong to the organization.
2. Project
Create one project per sending service. Each project has two environments: production and test.
| Environment | API key | Messages |
|---|---|---|
| Production | sk_live_… | Count toward your monthly limit and billing |
| Test | sk_test_… | Not counted |
3. API key
Create keys per environment under “API keys” in the dashboard. The key is shown once, when you create it. Choose from five scopes.
| Scope | Allows |
|---|---|
messages:send | Sending messages |
messages:retry | Retrying deliveries |
logs:read | Reading logs, consumers and endpoints |
endpoints:write | Creating, updating and deleting endpoints; rotating signing secrets |
consumers:write | Creating consumers |
Keys expire after 30 days, 90 days, 1 year or never. Owners and admins get an email 7 days before a key expires.
The samples on this page read the key from the WEBHOOK_ADMIN_API_KEY environment variable.
export WEBHOOK_ADMIN_API_KEY=sk_test_…4. Consumer and endpoint
A consumer is the company or user who receives your webhooks. Set external_id to the ID you use for that customer in your service.
curl https://api.webhookadmin.com/v1/consumers \
-H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "external_id": "cus_1024", "name": "Acme Inc." }'const res = await fetch('https://api.webhookadmin.com/v1/consumers', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ external_id: 'cus_1024', name: 'Acme Inc.' }),
});
const consumer = await res.json(); // { id: 'con_…', external_id: 'cus_1024', … }Register the URL that receives webhooks as an endpoint of that consumer. The secret in the response (the signing secret, starting with whsec_) is returned only here. The receiver uses it to verify signatures.
curl https://api.webhookadmin.com/v1/endpoints \
-H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consumer_id": "con_…",
"url": "https://example.com/webhooks",
"event_types": ["invoice.paid"]
}'const res = await fetch('https://api.webhookadmin.com/v1/endpoints', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
consumer_id: consumer.id,
url: 'https://example.com/webhooks',
event_types: ['invoice.paid'],
}),
});
const endpoint = await res.json(); // { id: 'ep_…', secret: 'whsec_…', … }- URLs must use https on port 443 or 8443. URLs with a raw IP address and names that resolve to private addresses are rejected.
- Leave out
event_typesto receive every event. - Set
fixed_ip: trueto deliver from the static source IP209.71.107.233. Ask the receiver to allow this IP.
5. Send
Send the consumer’s external_id, the event type and the payload to POST /v1/messages. Every endpoint of that consumer that accepts the event receives it.
curl https://api.webhookadmin.com/v1/messages \
-H "Authorization: Bearer $WEBHOOK_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: inv_88-paid" \
-d '{
"consumer": "cus_1024",
"event_type": "invoice.paid",
"payload": { "invoice_id": "inv_88", "amount": 128000 }
}'const res = await fetch('https://api.webhookadmin.com/v1/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WEBHOOK_ADMIN_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'inv_88-paid',
},
body: JSON.stringify({
consumer: 'cus_1024',
event_type: 'invoice.paid',
payload: { invoice_id: 'inv_88', amount: 128000 },
}),
});
const message = await res.json(); // { id: 'msg_…', deliveries: 1 }HTTP/1.1 202 Accepted
{ "id": "msg_…", "deliveries": 1 }- Requests with the same
Idempotency-Keyreturn the firstidfor 24 hours and create no second message. - If no consumer has that
external_id, one is created on the spot (with no endpoints, sodeliveriesis 0). - Check delivery under “Logs” in the dashboard or with
GET /v1/messages/{id}.
6. Verify the signature
Every delivery is signed in the Standard Webhooks format, so receivers can verify it with the official libraries. Headers and the signing scheme are described in Signature verification.
npm install standardwebhooks expressimport express from 'express';
import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.WEBHOOK_SECRET); // whsec_…
const app = express();
// Verify against the raw body, before any JSON parsing
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = wh.verify(req.body, req.headers);
console.log(event.type, event.data);
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
});
app.listen(3000);Set WEBHOOK_SECRET to the whsec_… you received in step 4.