Real-time Delivery
Events delivered instantly as they happen
Secure Signatures
HMAC-SHA256 signatures for verification
Automatic Retries
Failed deliveries retry with exponential backoff
Delivery Logs
Full visibility into delivery status
What are Webhooks?
Webhooks are HTTP callbacks that notify your server when specific events occur. Instead of continuously polling our API, you register a URL and we send you data in real-time.
Event Occurs
User signs up, payment succeeds, etc.
Your Server
Receives POST with event data
When to use Webhooks
Using the Hook
The useWebhooks hook provides a simple interface for managing webhooks in your React application.
'use client'
import { useWebhooks } from '@sylphx/platform-sdk/react'
export function WebhookSettings() {
const {
webhooks,
isLoading,
create,
update,
remove,
test,
} = useWebhooks()
const handleCreate = async () => {
await create({
url: 'https://api.example.com/webhooks',
events: ['user.created', 'user.updated'],
secret: 'whsec_...',
})
}
const handleTest = async (webhookId: string) => {
const result = await test(webhookId)
if (result.success) {
console.log('Webhook delivered successfully')
}
}
if (isLoading) return <div>Loading...</div>
return (
<div className="space-y-4">
<h2>Your Webhooks</h2>
{webhooks.map((webhook) => (
<div key={webhook.id} className="p-4 border rounded-lg">
<p className="font-mono text-sm">{webhook.url}</p>
<p className="text-sm text-muted-foreground">
Events: {webhook.events.join(', ')}
</p>
<div className="mt-2 flex gap-2">
<button onClick={() => handleTest(webhook.id)}>
Test
</button>
<button onClick={() => remove(webhook.id)}>
Delete
</button>
</div>
</div>
))}
<button onClick={handleCreate}>
Add Webhook
</button>
</div>
)
}Available Events
Subscribe to specific events or use wildcards to receive all events of a type.
| Event | Description |
|---|---|
user.created | A new user signed up |
user.updated | User profile was updated |
user.deleted | User account was deleted |
subscription.created | New subscription started |
subscription.updated | Subscription plan changed |
subscription.cancelled | Subscription was cancelled |
payment.succeeded | Payment was successful |
payment.failed | Payment attempt failed |
invoice.created | New invoice generated |
invoice.paid | Invoice was paid |
* | All events (wildcard) |
// Subscribe to specific events
await platform.webhooks.create({
url: 'https://api.example.com/webhooks',
events: [
'user.created',
'user.updated',
'payment.succeeded',
'payment.failed',
],
})
// Subscribe to all user events
await platform.webhooks.create({
url: 'https://api.example.com/webhooks',
events: ['user.*'],
})
// Subscribe to all events
await platform.webhooks.create({
url: 'https://api.example.com/webhooks',
events: ['*'],
})Payload Structure
Every webhook delivery includes a consistent payload structure with event metadata and the actual data.
{
"id": "evt_1234567890",
"type": "user.created",
"timestamp": "2024-01-15T10:30:00.000Z",
"version": "2024-01-01",
"data": {
"id": "usr_abc123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2024-01-15T10:30:00.000Z"
},
"metadata": {
"webhookId": "whk_xyz789",
"attempt": 1,
"environment": "production"
}
}| Property | Type | Description |
|---|---|---|
idrequired | string | Unique event identifier |
typerequired | string | Event type (e.g., user.created) |
timestamprequired | string | ISO 8601 timestamp of when the event occurred |
versionrequired | string | API version for the payload format |
datarequired | object | Event-specific data payload |
metadata | object | Delivery metadata including webhook ID and attempt number |
Security
Every webhook request includes a signature header that you should verify to ensure the request is authentic.
Always Verify Signatures
Signature Verification
The signature is sent in the X-Webhook-Signature header. Verify it using your webhook secret.
import crypto from 'crypto'
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
tolerance = 300 // 5 minutes
): boolean {
const [timestamp, hash] = signature.split(',')
const ts = parseInt(timestamp.replace('t=', ''), 10)
const sig = hash.replace('v1=', '')
// Check timestamp is within tolerance
const now = Math.floor(Date.now() / 1000)
if (Math.abs(now - ts) > tolerance) {
throw new Error('Webhook timestamp outside tolerance')
}
// Compute expected signature
const signedPayload = `${ts}.${payload}`
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex')
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(sig),
Buffer.from(expected)
)
}
// Usage in API route
export async function POST(req: Request) {
const payload = await req.text()
const signature = req.headers.get('X-Webhook-Signature')!
const secret = process.env.WEBHOOK_SECRET!
if (!verifyWebhookSignature(payload, signature, secret)) {
return new Response('Invalid signature', { status: 401 })
}
const event = JSON.parse(payload)
// Process the event...
return new Response('OK', { status: 200 })
}Request Headers
| Property | Type | Description |
|---|---|---|
X-Webhook-Signaturerequired | string | HMAC-SHA256 signature with timestamp |
X-Webhook-ID | string | Unique delivery ID for idempotency |
X-Webhook-Timestamp | string | Unix timestamp of the request |
Content-Type | string= application/json | Always application/json |
User-Agent | string | Sylphx-Webhook/1.0 |
Retry Policy
If your endpoint returns a non-2xx status code or times out, we automatically retry with exponential backoff.
1
Attempt
Immediate
2
Attempt
1 minute
3
Attempt
5 minutes
4
Attempt
30 minutes
5
Attempt
2 hours
Response Requirements
Handling Failures
// Respond quickly, process async
export async function POST(req: Request) {
const event = await verifyAndParseWebhook(req)
// Queue for background processing
await queue.add('process-webhook', {
eventId: event.id,
type: event.type,
data: event.data,
})
// Return immediately
return new Response('OK', { status: 200 })
}
// Process in background job
export async function processWebhook(job) {
const { eventId, type, data } = job.data
// Check for duplicate delivery
const processed = await db.processedEvents.findUnique({
where: { eventId },
})
if (processed) {
console.log('Duplicate event, skipping:', eventId)
return
}
// Process the event
await handleEvent(type, data)
// Mark as processed
await db.processedEvents.create({
data: { eventId, processedAt: new Date() },
})
}Server-side API
Manage webhooks programmatically from your server.
import { platform } from '@/lib/platform'
// Create a webhook
const webhook = await platform.webhooks.create({
url: 'https://api.example.com/webhooks',
events: ['user.created', 'payment.succeeded'],
description: 'Production webhook for user sync',
metadata: {
team: 'backend',
},
})
// List all webhooks
const webhooks = await platform.webhooks.list()
// Update a webhook
await platform.webhooks.update(webhook.id, {
events: ['user.*', 'payment.*'],
enabled: true,
})
// Delete a webhook
await platform.webhooks.delete(webhook.id)
// Test a webhook
const testResult = await platform.webhooks.test(webhook.id)
console.log('Test delivery:', testResult.success ? 'Success' : 'Failed')
// Get delivery logs
const logs = await platform.webhooks.getDeliveries(webhook.id, {
limit: 50,
status: 'failed',
})Best Practices
Verify Signatures
Always verify the webhook signature before processing. Never trust unverified payloads.
Respond Quickly
Return a 2xx response immediately, then process the event asynchronously in a background job.
Handle Duplicates
Use the event ID for idempotency. The same event may be delivered multiple times.
Monitor Deliveries
Regularly check delivery logs and set up alerts for failed webhooks.