Skip to main content

Webhooks Overview

Webhooks allow you to receive real-time notifications when events occur in your Fanfare organization. Instead of polling the API, webhooks push events to your server as they happen.

How Webhooks Work

  1. You configure a webhook endpoint URL in your Fanfare dashboard
  2. When an event occurs, Fanfare sends an HTTP POST request to your endpoint
  3. Your server processes the event and responds with a 2xx status code
  4. If delivery fails, Fanfare retries with exponential backoff
┌─────────────┐     Event     ┌─────────────┐     POST     ┌─────────────┐
│   Fanfare   │──────────────>│   Webhook   │─────────────>│ Your Server │
│   Platform  │               │   Service   │              │             │
└─────────────┘               └─────────────┘              └─────────────┘

                                    │ Retry on failure
                                    └──────────────────────────────────────┐


Webhook Payload

All webhook payloads follow a consistent structure:
{
  "id": "whk_01HXYZ123456789",
  "type": "queue.consumer.admitted",
  "timestamp": "2024-12-01T09:15:00Z",
  "organizationId": "org_01HXYZ123456789",
  "data": {
    "queueId": "queue_01HXYZ123456789",
    "consumerId": "cons_01HXYZ123456789",
    "admittedAt": "2024-12-01T09:15:00Z",
    "admissionToken": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
  }
}

Payload Fields

FieldTypeDescription
idstringUnique webhook delivery ID
typestringEvent type (e.g., queue.consumer.admitted)
timestampstringISO 8601 timestamp of the event
organizationIdstringYour organization ID
dataobjectEvent-specific data

Event Categories

Queue Events

EventDescription
queue.consumer.enteredConsumer joined a queue
queue.consumer.admittedConsumer admitted from queue
queue.consumer.completedConsumer completed checkout
queue.consumer.leftConsumer left the queue
queue.consumer.deniedConsumer was denied entry

Draw Events

EventDescription
draw.consumer.enteredConsumer entered a draw
draw.consumer.wonConsumer won the draw
draw.consumer.lostConsumer did not win
draw.completedDraw has been executed

Auction Events

EventDescription
auction.bid.placedBid was placed
auction.bid.outbidBidder was outbid
auction.consumer.wonConsumer won auction
auction.settledAuction has settled

Order Events

EventDescription
order.createdNew order created
order.completedOrder completed
order.cancelledOrder was cancelled

Consumer Events

EventDescription
consumer.createdNew consumer created
consumer.updatedConsumer profile updated

Distribution Events

EventDescription
distribution.updatedDistribution timing/settings changed
distribution.openedDistribution is now open
distribution.closedDistribution has closed

Configuring Webhooks

Via Dashboard

  1. Navigate to Settings > Webhooks in your Fanfare dashboard
  2. Click “Add Endpoint”
  3. Enter your webhook URL (must be HTTPS)
  4. Select the events you want to receive
  5. Save the endpoint

Via API

curl -X POST https://admin.fanfare.io/api/v1/webhooks \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/fanfare",
    "events": ["queue.consumer.admitted", "order.created"],
    "secret": "whsec_your_signing_secret"
  }'

Receiving Webhooks

Basic Handler

import express from "express";
import crypto from "crypto";

const app = express();

app.post("/webhooks/fanfare", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-fanfare-signature"] as string;
  const timestamp = req.headers["x-fanfare-timestamp"] as string;

  // Verify signature
  const expectedSignature = crypto
    .createHmac("sha256", process.env.WEBHOOK_SECRET!)
    .update(`${timestamp}.${req.body}`)
    .digest("hex");

  if (signature !== `sha256=${expectedSignature}`) {
    return res.status(401).send("Invalid signature");
  }

  // Parse the event
  const event = JSON.parse(req.body.toString());

  // Handle the event
  switch (event.type) {
    case "queue.consumer.admitted":
      handleAdmission(event.data);
      break;
    case "order.created":
      handleOrder(event.data);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  // Respond to acknowledge receipt
  res.status(200).send("OK");
});

app.listen(3000);

Async Processing

For production, process webhooks asynchronously:
import { Queue } from "bullmq";

const webhookQueue = new Queue("webhooks");

app.post("/webhooks/fanfare", async (req, res) => {
  // Verify signature first
  if (!verifySignature(req)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());

  // Queue for processing
  await webhookQueue.add("process", event, {
    removeOnComplete: 1000,
    attempts: 3,
  });

  // Respond immediately
  res.status(200).send("OK");
});

Webhook Headers

Each webhook request includes:
HeaderDescription
Content-Typeapplication/json
X-Fanfare-SignatureHMAC-SHA256 signature
X-Fanfare-TimestampUnix timestamp of request
X-Fanfare-Event-TypeEvent type
X-Fanfare-Delivery-IdUnique delivery ID
User-AgentFanfare-Webhooks/1.0

Best Practices

1. Respond Quickly

Respond with a 2xx status code within 30 seconds. Process events asynchronously for longer operations.

2. Handle Duplicates

Webhooks may be delivered more than once. Use the event id to deduplicate:
const processedEvents = new Set();

function handleEvent(event) {
  if (processedEvents.has(event.id)) {
    return; // Already processed
  }
  processedEvents.add(event.id);
  // Process event...
}

3. Verify Signatures

Always verify the webhook signature before processing. See Webhook Signatures.

4. Use HTTPS

Webhook endpoints must use HTTPS. HTTP URLs will be rejected.

5. Handle Retries

If your endpoint fails, Fanfare will retry. See Retry Policy.

Testing Webhooks

Using the Dashboard

  1. Go to Settings > Webhooks
  2. Select your endpoint
  3. Click “Send Test Event”
  4. Choose an event type to test

Using CLI

# Send a test webhook
curl -X POST https://admin.fanfare.io/api/v1/webhooks/test \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "endpointId": "whe_01HXYZ123456789",
    "eventType": "queue.consumer.admitted"
  }'

Local Development

For local development, use a tunneling service:
# Using ngrok
ngrok http 3000

# Configure webhook URL as: https://your-subdomain.ngrok.io/webhooks/fanfare

Webhook Events Reference

For detailed information about each event type and its payload, see: