Skip to main content

Webhooks

Toffee uses webhooks to notify your backend when important events occur, such as offer engagements and completions. All webhooks are signed for security.

Webhook Structure

All Toffee webhooks follow a consistent structure with four key fields:

{
"id": "wh_01j9x5k2m8n4p6q7r8s9t0v1w2",
"event": "event.type",
"timestamp": "2023-06-01T12:05:00Z",
"data": {
// Event-specific data
}
}

Fields:

  • id: Unique webhook identifier for idempotency handling
  • event: Event type (e.g., offer.shown, offer.redeemed)
  • timestamp: ISO 8601 timestamp when the event occurred
  • data: Event-specific payload containing relevant entity data

Webhook Events

Toffee sends webhooks for various events throughout the offer lifecycle:

  • Offer Events: See the Offers page for offer-related events

Sample Webhook Payloads

offer.shown

{
"id": "wh_01j9x5k2m8n4p6q7r8s9t0v1w2",
"event": "offer.shown",
"timestamp": "2023-06-01T14:22:00Z",
"data": {
"offer_id": "offer_abc123"
}
}

offer.engaged

{
"id": "wh_01j9x5k2m8n4p6q7r8s9t0v1w2",
"event": "offer.engaged",
"timestamp": "2023-06-01T14:23:15Z",
"data": {
"offer_id": "offer_abc123"
}
}

Webhook Signature Verification

All webhooks include a signature in the X-Toffee-Signature header that you must verify to ensure the webhook is authentic.

Header Format:

X-Toffee-Signature: 6aeedfabc12345...

Verification Steps

  1. Compute the expected signature: Use HMAC SHA256 with your webhook secret and the raw request body:
    expected_signature = HMAC_SHA256(your_webhook_secret, raw_body)
  2. Compare signatures: Use a constant-time comparison to compare the expected signature with the header value

Example Verification (TypeScript)

import { createHmac, timingSafeEqual } from 'crypto';

function verifyToffeeWebhook(
rawPayload: string,
signatureHeader: string,
webhookSecret: string
): boolean {
// Compute expected signature
const expectedSignature = createHmac('sha256', webhookSecret)
.update(rawPayload, 'utf8')
.digest('hex');

// Compare signatures (constant-time)
const expectedBuffer = Buffer.from(expectedSignature, 'hex');
const signatureBuffer = Buffer.from(signatureHeader, 'hex');

return expectedBuffer.length === signatureBuffer.length &&
timingSafeEqual(expectedBuffer, signatureBuffer);
}


Webhook Configuration

You can configure and manage your webhook settings:

  1. Webhook URL: The endpoint where Toffee will send webhooks
  2. Webhook Secret: Used to sign webhook payloads (can be rotated)
  3. Event Selection (soon): Choose which events to receive (offers, or other future event types)

Best Practices

  1. Always verify signatures: Never process webhooks without signature verification
  2. Handle duplicate events: While rare, webhooks may be sent multiple times, implement idempotency using webhook IDs
  3. Respond quickly: Return a 200 status code within 10 seconds to acknowledge receipt
  4. Retry logic: Toffee will retry failed webhooks, but implement your own retry logic for critical operations
  5. Log webhook events: Keep audit logs of all webhook events for debugging and compliance
  6. Use HTTPS: Always use HTTPS endpoints for webhook URLs

Troubleshooting

Common Issues

  • Signature verification fails: Check that you're using the correct webhook secret and following the verification steps exactly
  • Timeout errors: Ensure your webhook endpoint responds within 10 seconds
  • Missing webhooks: Check your webhook URL configuration and ensure your endpoint is accessible

Testing Webhooks

Use tools like ngrok for local development to expose your local webhook endpoint to Toffee:

ngrok http 3000