Webhooks
Receive real-time notifications when referral events occur.
Webhooks allow you to receive real-time HTTP notifications when events occur in your referral program. This is the recommended way to handle reward fulfillment.
Setting Up Webhooks
- Go to Settings → Webhooks in your dashboard
- Click Add Endpoint
- Enter your webhook URL (must be HTTPS)
- Select the events you want to receive
- Click Create Webhook
Webhook Payload
All webhook payloads follow this structure:
{
"id": "evt_xxxxx",
"type": "referral.converted",
"created": "2024-01-15T10:30:00Z",
"data": {
// Event-specific data
}
}Event Types
referral.clicked
Sent when someone clicks a referral link.
{
"id": "evt_xxxxx",
"type": "referral.clicked",
"created": "2024-01-15T10:30:00Z",
"data": {
"referralCode": "abc123",
"referrerId": "user_123",
"campaignId": "camp_xxxxx",
"clickId": "click_xxxxx",
"metadata": {
"userAgent": "Mozilla/5.0...",
"country": "US",
"device": "mobile"
}
}
}referral.converted
Sent when a referred user completes the reward trigger action.
{
"id": "evt_xxxxx",
"type": "referral.converted",
"created": "2024-01-15T10:30:00Z",
"data": {
"referralCode": "abc123",
"referrerId": "user_123",
"referredUserId": "user_456",
"campaignId": "camp_xxxxx",
"conversionType": "signup",
"metadata": {
"email": "newuser@example.com"
}
}
}reward.earned
Sent when a reward is earned (for either referrer or referred user).
{
"id": "evt_xxxxx",
"type": "reward.earned",
"created": "2024-01-15T10:30:00Z",
"data": {
"rewardId": "reward_xxxxx",
"userId": "user_123",
"type": "referrer", // or "referred"
"amount": 10,
"currency": "USD",
"rewardType": "credit",
"referredUserId": "user_456",
"campaignId": "camp_xxxxx"
}
}Verifying Signatures
All webhooks include a signature in the X-Referral-Signature header. Always verify this signature to ensure the request came from Referral Engine.
import crypto from 'crypto'
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}
// In your webhook handler
export async function POST(request: Request) {
const payload = await request.text()
const signature = request.headers.get('X-Referral-Signature')
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 })
}
const event = JSON.parse(payload)
// Process the event...
}Handling Webhooks
Best practices for webhook handlers:
- Respond quickly — Return a 2xx response as soon as possible, then process asynchronously
- Handle duplicates — Use the event ID to deduplicate (we may retry on network issues)
- Verify signatures — Always verify the webhook signature
- Log everything — Store raw payloads for debugging
export async function POST(request: Request) {
const payload = await request.text()
const signature = request.headers.get('X-Referral-Signature')
// Verify signature
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return new Response('Invalid signature', { status: 401 })
}
const event = JSON.parse(payload)
// Check for duplicate (idempotency)
const processed = await db.webhookEvents.findUnique({
where: { eventId: event.id }
})
if (processed) {
return Response.json({ received: true })
}
// Store the event
await db.webhookEvents.create({
data: { eventId: event.id, payload: event }
})
// Process asynchronously
await queue.add('process-webhook', event)
// Respond immediately
return Response.json({ received: true })
}Testing Webhooks
Use the Test button in your webhook settings to send a test event. You can also use tools like ngrok to expose your local development server.
Retry Policy
If your endpoint fails to respond with a 2xx status, we'll retry:
- Attempt 1: Immediately
- Attempt 2: 1 minute later
- Attempt 3: 5 minutes later
- Attempt 4: 30 minutes later
- Attempt 5: 2 hours later
After 5 failed attempts, the webhook is marked as failed. You can manually retry from the webhook logs in your dashboard.