# 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.

```http
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.

<Callout type="info">
  Use `BeeL-Event-Id` for deduplication (same across retries). Use `BeeL-Delivery-Id` to correlate with specific delivery log entries in the dashboard.
</Callout>

## 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

```js
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

```python
@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: once a delivery has succeeded for a given `BeeL-Event-Id`, that event is not re-enqueued for automatic retry.

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

```js
// ✅ 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](/guides/idempotency).

## Related

<Related>

- [Retries & delivery logs](/webhooks/retries) — why the same event can arrive twice
- [Signature verification](/webhooks/signatures) — verify before you deduplicate
- [Idempotency](/guides/idempotency) — the same idea for your requests

</Related>

---

Full OpenAPI spec: https://docs.beel.es/api/openapi