Overview
Receive real-time notifications in your application when events happen in BeeL.
Webhooks let your application receive HTTP notifications the moment something happens in BeeL. — no polling required. When an event occurs (e.g. AEAT processes a VeriFactu submission), BeeL. sends an HTTP POST to the URL you registered with a signed JSON payload.
API plan exclusive: Webhooks are available only with the API plan. See Pricing for details.
Scopes
Every webhook endpoint is reachable with an API key — none of them needs a dashboard
session. Two scopes gate them, webhooks:read for looking and webhooks:write for
changing; each webhook operation states the one it
requires, and you grant them to your key like any other scope.
webhooks:write covers rotating the secret. A key that can create subscriptions
can also invalidate the secret every existing receiver verifies against. If you issue
keys to third parties, that is the reason to keep webhook management on a separate
key from the one that issues invoices.
Quick Start
1. Create a webhook subscription
Requires webhooks:write.
curl -X POST "https://app.beel.es/api/v1/accounts/{account_id}/webhooks" \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/beel",
"events": ["invoice.issued", "invoice.email.sent", "invoice.voided", "verifactu.status.updated"]
}'The response contains a secret field — copy it now. It will not be shown again.
{
"success": true,
"data": {
"id": "3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"url": "https://yourapp.com/webhooks/beel",
"events": ["invoice.issued", "invoice.email.sent", "invoice.voided", "verifactu.status.updated"],
"active": true,
"account_relationship": "own",
"secret": "whsec_a3f5b2e1c9d8f7e6b5a4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2",
"last_used_at": null,
"created_at": "2026-03-08T10:00:00Z"
}
}Managing accounts on someone's behalf? Add "account_relationship": "managed"
(or "all") to also receive events from the accounts you provisioned. It defaults to
own. See Events for how to route them on
arrival.
Store the secret securely! It's only shown once and required to verify webhook signatures.
2. Handle incoming requests
Your endpoint must:
- Accept
POSTrequests withContent-Type: application/json - Verify the signature using the
BeeL-Signatureheader - Return
HTTP 2xxwithin 10 seconds
// Express.js example
app.post('/webhooks/beel', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['beel-signature'];
if (!verifySignature(req.body, signature, process.env.BEEL_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
switch (event.type) {
case 'invoice.issued':
handleNewInvoice(event.data);
break;
case 'invoice.email.sent':
handleEmailSent(event.data);
break;
case 'invoice.voided':
handleVoided(event.data);
break;
case 'verifactu.status.updated':
handleVeriFactuUpdate(event.data);
break;
}
res.status(200).send('OK');
});3. Test your endpoint
POST /v1/accounts/{account_id}/webhooks/{webhook_id}/test — webhooks:write. Fires a synthetic,
fully signed payload to your endpoint immediately, outside the normal delivery queue.
curl -X POST "https://app.beel.es/api/v1/accounts/{account_id}/webhooks/3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/test" \
-H "Authorization: Bearer beel_sk_live_xxx"{
"success": true,
"data": {
"delivery_success": true,
"http_status": 200,
"duration_ms": 142,
"error": null
}
}The call answers 200 whenever the test ran — branch on delivery_success for the
outcome, not on the HTTP status. A delivery your endpoint rejected is a successful test
run, not an error. Test events are not retried and do not appear in the delivery
history.
The same thing is available as the Send test event button in the BeeL. dashboard (Developers → Webhooks → your webhook).
Don't have an endpoint yet? Use webhook.site to get a free temporary URL that captures and displays incoming requests. It's a great way to inspect webhook payloads and verify that events are being delivered correctly before building your handler.
Payload Structure
Every webhook event shares the same envelope:
// envelope; the `data` block depends on the event type — see Events.
{
"id": "3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"type": "verifactu.status.updated",
"created_at": "2026-03-07T16:20:26Z",
"api_version": "2025-01",
"livemode": true,
"company_id": "9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"nif": "B12345674",
"account_id": "4d6d8dca-689a-44f4-bafc-88fa8a1bc34b",
"account_external_ref": "acct-2041",
"account_relationship": "own",
"data": { /* event-specific payload */ }
}Every delivery carries the same envelope fields: id, type, created_at, api_version, livemode, test, company_id, nif, account_id, account_external_ref, account_relationship.
Two of them behave differently from the rest and decide how you write your handler:
idis stable across every retry attempt of the same logical event, so it is the key to deduplicate on — see Deduplication.company_idis what you route on. One endpoint receives the events of every NIF you operate;nullsimply means the event is not about a single company.
Field-by-field descriptions are on Events, which
also documents the data block for each event type.
Limits
| Parameter | Value |
|---|---|
| Max subscriptions per account | 10 |
| Max active subscriptions | 10 |
| Required URL scheme | https:// |
| Delivery log history | Last 50 per subscription |
Delivery timeouts and attempt counts are part of the retry policy — see Retries.
Headers Sent on Every Delivery
| Header | Description |
|---|---|
Content-Type | application/json |
BeeL-Signature | HMAC-SHA256 signature — see Signatures |
BeeL-Event | Event type (e.g. invoice.issued) |
BeeL-Event-Id | UUID identifying this logical event. Identical across all retry attempts. Matches the id field in the payload. |
BeeL-Delivery-Id | UUID unique to this specific delivery attempt. Matches the delivery log id in the dashboard. |
Idempotency-Key | Same value as BeeL-Event-Id — for automatic deduplication by frameworks |
Using the Official SDK
The Node.js SDK includes built-in webhook verification:
import { WebhookVerifier } from '@beel_es/sdk';
const verifier = new WebhookVerifier(process.env.BEEL_WEBHOOK_SECRET);
app.post('/webhooks/beel', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifier.verify(
req.body.toString('utf8'),
req.headers['beel-signature']
);
// Event is verified and typed
if (event.type === 'verifactu.status.updated') {
console.log('Invoice:', event.data.invoice_number);
console.log('Status:', event.data.new_status);
}
res.status(200).send('OK');
} catch (error) {
res.status(400).send('Invalid signature');
}
});See the SDKs documentation for more details.
What's Next
- Events — Available event types and their data payloads
- Signatures — Verify that requests come from BeeL.
- Retries — How failed deliveries are retried
- Deduplication — Safely handle duplicate deliveries
Questions? Email us at it@beel.es.