Last Updated: 1/27/2026
Webhooks
Receive real-time notifications when events occur.
Overview
Webhooks allow your application to receive HTTP callbacks when events happen in your account.
Your App <──── POST /webhook ──── Example API
(event data):::info
Webhooks are sent as POST requests with a JSON body containing event data.
:::
Supported Events
| Event | Description | Payload |
|---|---|---|
user.created | New user registered | User object |
user.updated | User profile changed | User object |
user.deleted | User account removed | User ID |
payment.completed | Payment succeeded | Payment object |
payment.failed | Payment failed | Payment + error |
Setting Up Webhooks
1. Create Endpoint
// Express.js example
import express from 'express';
import { verifyWebhook, type WebhookEvent } from '@example/sdk';
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'] as string;
const payload = req.body;
try {
const event = verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET!);
handleEvent(event);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook verification failed:', error);
res.status(400).send('Invalid signature');
}
});:::danger Always verify webhook signatures. Without verification, attackers could send fake events. :::
2. Register Webhook URL
const client = createClient({ apiKey: process.env.API_KEY! });
const webhook = await client.webhooks.create({
url: 'https://yourapp.com/webhook',
events: ['user.created', 'user.updated', 'payment.completed'],
secret: 'whsec_your_secret_here',
});
console.log('Webhook ID:', webhook.id);3. Handle Events
import type { WebhookEvent } from '@example/sdk';
function handleEvent(event: WebhookEvent) {
switch (event.type) {
case 'user.created':
console.log('New user:', event.data.id);
sendWelcomeEmail(event.data.email);
break;
case 'user.updated':
console.log('User updated:', event.data.id);
syncUserData(event.data);
break;
case 'payment.completed':
console.log('Payment received:', event.data.amount);
fulfillOrder(event.data.orderId);
break;
case 'payment.failed':
console.error('Payment failed:', event.data.error);
notifyCustomer(event.data.customerId);
break;
default:
console.log('Unhandled event:', event.type);
}
}:::tip Use a switch statement or event handler map for clean, maintainable webhook handling. :::
Webhook Payload
{
"id": "evt_abc123",
"type": "user.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "user_xyz789",
"email": "alice@example.com",
"name": "Alice Smith",
"createdAt": "2024-01-15T10:30:00Z"
}
}Signature Verification
import crypto from 'crypto';
function verifySignature(
payload: Buffer,
signature: string,
secret: string,
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}:::warning
Use crypto.timingSafeEqual to prevent timing attacks when comparing signatures.
:::
Retry Policy
Failed webhook deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
:::note After 5 failed attempts, the webhook is marked as failed. Check the dashboard for failed deliveries. :::
Best Practices
- Respond quickly - Return 200 within 5 seconds
- Process async - Queue events for background processing
- Handle duplicates - Events may be sent more than once
- Log everything - Store event IDs for debugging
// Idempotent event handling
const processedEvents = new Set<string>();
function handleEvent(event: WebhookEvent) {
if (processedEvents.has(event.id)) {
console.log('Duplicate event, skipping:', event.id);
return;
}
processedEvents.add(event.id);
// Process event...
}