Webhooks

Deduplication

Safely handle duplicate webhook deliveries using the BeeL-Event-Id header.


Network issues and retries mean your endpoint may receive the same event more than once. BeeL. provides two mechanisms to help you deduplicate deliveries safely.

The BeeL-Event-Id Header

Every delivery attempt for the same logical event carries the same BeeL-Event-Id UUID, regardless of how many times BeeL has tried. This value matches the id field in the JSON payload.

BeeL-Event-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
BeeL-Delivery-Id: 8ee6b023-c4e5-482e-93ca-dc66da2f9cb5
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
  • BeeL-Event-Id — Identifies the logical event. Same across all retries.
  • BeeL-Delivery-Id — Identifies this specific HTTP attempt. Unique per call. Matches the id in the delivery log.
  • Idempotency-Key — Same value as BeeL-Event-Id, included for framework compatibility.

Use BeeL-Event-Id for deduplication (same across retries). Use BeeL-Delivery-Id to correlate with specific delivery log entries in the dashboard.

How to Deduplicate

Store the BeeL-Event-Id value the first time you process an event successfully and discard subsequent deliveries with the same ID.

Node.js with Redis

app.post('/webhooks/beel', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!verifyBeelSignature(req.body, req.headers['BeeL-Signature'], SECRET)) {
    return res.status(401).send('Unauthorized');
  }

  const eventId = req.headers['beel-event-id'];
  const event = JSON.parse(req.body.toString());

  // Check for duplicate
  const alreadyProcessed = await redis.exists(`webhook:${eventId}`);
  if (alreadyProcessed) {
    return res.status(200).send('Already processed');
  }

  await handleEvent(event);

  // Mark as processed — keep for 7 days
  await redis.setEx(`webhook:${eventId}`, 7 * 24 * 60 * 60, '1');

  res.status(200).send('OK');
});

Python with a database

@app.post("/webhooks/beel")
async def webhook(request: Request):
    raw_body = await request.body()
    event_id = request.headers.get("beel-event-id")

    if not verify_beel_signature(raw_body, request.headers.get("BeeL-Signature"), SECRET):
        raise HTTPException(status_code=401)

    # Check duplicate
    if await db.processed_webhooks.find_one({"event_id": event_id}):
        return {"ok": True}

    event = json.loads(raw_body)
    await handle_event(event)

    await db.processed_webhooks.insert_one({
        "event_id": event_id,
        "processed_at": datetime.utcnow()
    })

    return {"ok": True}

Server-Side Deduplication

BeeL also deduplicates on its end: if a delivery has already succeeded for a given BeeL-Event-Id, BeeL will not re-enqueue it for automatic retry even if the outbox processes the event more than once.

Combined with your own BeeL-Event-Id check, this gives you end-to-end exactly-once processing.

Best Practices

  • Always deduplicate — never assume an event arrives exactly once
  • Acknowledge first, process later — return 200 OK immediately and handle the event in a background job
  • Make your handlers idempotent — even if a duplicate slips through, applying the same state change twice should be safe
// ✅ Idempotent: setting a field is safe to repeat
async function handleVeriFactuUpdate(data) {
  await db.invoices.updateOne(
    { _id: data.invoice_id },
    { $set: { verifactu_status: data.new_status, qr_url: data.qr_url } }
  );
}

// ❌ Not idempotent: inserting without a duplicate check creates duplicates
async function handleVeriFactuUpdate(data) {
  await db.audit_logs.insertOne({ invoice_id: data.invoice_id, status: data.new_status });
}

For general API idempotency (preventing duplicate resource creation via Idempotency-Key), see Idempotency.