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-ef1234567890BeeL-Event-Id— Identifies the logical event. Same across all retries.BeeL-Delivery-Id— Identifies this specific HTTP attempt. Unique per call. Matches theidin the delivery log.Idempotency-Key— Same value asBeeL-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 OKimmediately 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.