NewStripe: a buyer with a NIF always gets a standard invoice, and the simplified threshold is deprecated
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog
Webhooks

Retries & delivery logs

How BeeL. retries failed deliveries and how to inspect the delivery history.


BeeL. automatically retries webhook deliveries that fail due to network errors, a 5xx response, or a 408 / 429. You can also inspect the delivery history and trigger manual retries from the dashboard or the API.

Retry policy

ParameterValue
Max attempts7 (1 original + the rest automatic retries)
Retry strategyFixed schedule: few attempts, spread out, see Retry schedule
Timeout per attempt10 seconds
Whole automatic retry window67 hours 11 minutes from the first attempt to the last (about 3 days)
SandboxOnly the first 3 attempts, over in 11 minutes

Every attempt, automatic or manual, is signed afresh with the current time — see Every attempt is signed afresh.

Retry schedule

AttemptWait after the previous attemptSince the first attemptSandbox
1st (original)Immediately—Yes
2nd1 minute1 minuteYes
3rd10 minutes11 minutesYes
4th1 hour1 hour 11 minutesNo
5th6 hours7 hours 11 minutesNo
6th24 hours31 hours 11 minutesNo
7th36 hours67 hours 11 minutesNo

If all 7 attempts fail, no further automatic retries occur. You can still trigger a manual retry from the dashboard or API at any time.

How long the retries last

In production the automatic retries span about 3 days: the last attempt goes out 67 hours 11 minutes after the first. That rides out a dropped connection, a bad deploy or an outage of a day or two. In sandbox they stop after 3 attempts, 11 minutes after the first, so testing never leaves events waiting for days.

If your endpoint is down for longer than that, every automatic attempt of the events in that window fails, and BeeL. does not try them again on its own.

Plan for it:

  1. Know you missed something. Watch consecutive_failures and last_error_cause on the subscription (see Reading the health fields), or the email BeeL. sends when the endpoint keeps failing.
  2. Replay what failed. List the deliveries of the subscription with List webhook delivery logs, pick the attempts with success: false from the outage, and retry each with a manual retry. There is no age limit on a manual retry: any delivery in the log can be retried while its subscription exists.
  3. Or reconcile from the resources. A webhook is a notification, not the record. Re-reading the invoices that changed during the outage — for example the list filters of Reconcile, do not only listen — gives you the same state without replaying anything. Pace that job to the rate limits.

Deduplicate on BeeL-Event-Id either way: a replayed event you already processed must be a no-op — see Deduplication.

What triggers a retry

ScenarioRetried?
Your server returns 5xx✅ Yes
Your server returns 408 Request Timeout or 429 Too Many Requests✅ Yes, on the same schedule
Connection refused / DNS failure / TLS error / timeout✅ Yes
Your server returns any other 4xx❌ No — treated as a permanent rejection of that delivery
Your server returns 2xx❌ No — delivery successful

If your endpoint returns a 4xx other than 408 or 429, BeeL. assumes the payload is the problem and will not retry. Return 5xx if you need BeeL. to retry (e.g. your database is temporarily unavailable), and 429 if you want BeeL. to slow down rather than drop the event.

Endpoint health and automatic pause

Besides retrying each delivery, BeeL. tracks whether your endpoint is receiving anything at all. A delivery counts as failed once it is given up on — it used all 7 attempts, or your endpoint rejected it with a non-retryable 4xx. Any successful delivery resets the count.

Consecutive failed deliveriesWhat happens
5The subscription's owner receives an email saying the endpoint is failing, with the last error. One email per run of failures.
25, over a run lasting more than 48 hoursBeeL. pauses the subscription and emails the owner again. Both conditions must hold: a busy endpoint that fails 25 times in an afternoon while you fix it is not paused.

A paused subscription stops receiving events. Events that happen while it is paused are not queued for later, and deliveries still waiting for an automatic retry when it is paused are dropped, even if you reactivate it before their turn. The same happens when you turn a subscription off or delete it. Those deliveries stay in the log, so you can retry them manually.

Reading the health fields

Every webhook subscription carries its health, so you can monitor it without waiting for the email:

FieldMeaning
consecutive_failuresFailed deliveries in a row. A delivery that exhausted its 7 attempts counts as one. 0 on a healthy endpoint.
last_errorThe last error seen in the current run of failures, as text for a human.
last_error_causeThe same error as a stable code (dns, connection, tls, timeout, http_client_error, http_server_error, invalid_url, forbidden_target, unknown). Branch on this, never on the wording of last_error.
activefalse once the subscription is paused or turned off.
deactivated_bybeel when BeeL. paused it for failing; owner when you turned it off. Empty while it is active.
deactivated_atWhen it was turned off. null while it is active.

last_error and last_error_cause are cleared, together with consecutive_failures, by the first successful delivery.

Reactivating a paused subscription

Send "active": true with Update a webhook subscription. If BeeL. paused it (deactivated_by: beel), the same call first sends a signed test delivery to your URL:

  • The endpoint answers 2xx → the subscription is active again and its failure count starts from zero.
  • Anything else → the call is rejected with 422 WEBHOOK_REACTIVATION_REQUIRES_LIVE_ENDPOINT and the subscription stays paused. Fix the endpoint and try again.

A subscription you turned off yourself (deactivated_by: owner) reactivates without a test.

Delivery logs

BeeL. keeps a log of every delivery attempt per subscription, newest first. Each log entry records:

  • Delivery timestamp and duration
  • HTTP status code and response body
  • All request headers sent (including BeeL-Signature, BeeL-Event-Id, BeeL-Delivery-Id, BeeL-Event)
  • The full JSON payload that was sent
  • The attempt number
  • Error description (for failed deliveries)

View delivery logs

GET /v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries — webhooks:read. Newest first, paginated. Query params: page, limit, event_type (only deliveries of one event type) and event_id (only deliveries of one event — use it to follow every attempt on a single event without paging through the whole history).

curl "https://app.beel.es/api/v1/accounts/{account_id}/webhooks/3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/deliveries?event_type=verifactu.status.updated" \
  -H "Authorization: Bearer $BEEL_API_KEY"
{
  "success": true,
  "data": {
    "deliveries": [
      {
        "id": "8ee6b023-c4e5-482e-93ca-dc66da2f9cb5",
        "subscription_id": "3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
        "webhook_event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "event_type": "verifactu.status.updated",
        "attempt_number": 1,
        "http_status": 200,
        "success": true,
        "duration_ms": 142,
        "response_body": "OK",
        "response_body_truncated": false,
        "response_body_length": 2,
        "request_headers": {
          "BeeL-Signature": "t=1741362026,v1=3c4f7a2e...",
          "BeeL-Event": "verifactu.status.updated",
          "BeeL-Event-Id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "BeeL-Delivery-Id": "8ee6b023-c4e5-482e-93ca-dc66da2f9cb5",
          "Idempotency-Key": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        },
        "payload": "{\"id\":\"a1b2c3d4...\",\"type\":\"verifactu.status.updated\",...}",
        "delivered_at": "2026-03-07T16:20:26Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 1,
      "total_items": 1,
      "items_per_page": 20,
      "has_next": false,
      "has_previous": false
    }
  }
}

Reading a log entry

Every field of the entry is described in List webhook delivery logs. What the schema cannot tell you is which of them answer which question:

  • "Is this the same event I already saw?" — webhook_event_id groups every attempt at one logical event and matches the BeeL-Event-Id header; id identifies this attempt and matches BeeL-Delivery-Id. Deduplicate on the former, correlate your server logs with the latter.
  • "Did my endpoint refuse it, or never see it?" — http_status is null when the connection itself failed (DNS, timeout, TLS), and error_message says which. On a successful attempt error_message is absent. A populated http_status means your server answered and the payload reached it.
  • "Can I replay it myself?" — request_headers and payload hold the exact bytes and signature that were sent, so you can re-run your verification offline against a delivery that failed, without waiting for the next one. Skip the timestamp check when you do: the logged t is as old as the attempt, so it is outside the replay window.

Manual retry

POST /v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry — webhooks:write. Triggers a retry for any delivery, successful or not:

curl -X POST \
  "https://app.beel.es/api/v1/accounts/{account_id}/webhooks/3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/deliveries/8ee6b023-c4e5-482e-93ca-dc66da2f9cb5/retry" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

This uses the original payload and generates a new signature with a fresh timestamp. It runs right away and answers with the new delivery log entry — the next attempt_number, the same webhook_event_id and BeeL-Event-Id, a new BeeL-Delivery-Id. Branch on its success, not on the HTTP status. A manual retry that fails does not add to consecutive_failures.

Endpoint requirements

To ensure reliable delivery, your endpoint should:

  • Respond within 10 seconds — BeeL. does not wait longer
  • Return 2xx immediately and process the event asynchronously if needed
  • Return 5xx if you are temporarily unable to process (triggers a retry)
  • Return 429 if you need BeeL. to slow down (triggers a retry)
  • Not return any other 4xx unless the payload itself is the problem
// ✅ Good: acknowledge immediately, process async
app.post('/webhooks/beel', async (req, res) => {
  res.status(200).send('OK'); // respond first
  await queue.push(req.body); // process later
});

// ❌ Bad: synchronous processing blocks the response
app.post('/webhooks/beel', async (req, res) => {
  await processEventSynchronously(req.body); // may exceed 10s timeout
  res.status(200).send('OK');
});