> ## Documentation Index
> Fetch the complete documentation index at: https://developers.bloobank.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks overview

> Receive real-time events from BlooBank — how it works, the envelope, idempotency, and best practices.

Webhooks let BlooBank push events to your service in real time. Instead of polling endpoints to discover state changes, your server receives a signed HTTP `POST` at the moment of every lifecycle transition — payment order created, approved, processing, settled, failed, expired.

<Note>
  The webhook signing and retry policies below are **proposed defaults** aligned with industry conventions (Stripe, GitHub). The concrete header names and retry intervals will be finalized in coordination with the BlooBank platform team before exiting "Proposed" status.
</Note>

## How it works

<Steps>
  <Step title="Register your endpoint">
    Configure your webhook endpoint URL in the BlooBank Dashboard. Each environment (sandbox / production) holds its own configuration.
  </Step>

  <Step title="BlooBank delivers events">
    On every lifecycle transition, BlooBank sends a `POST` request with a JSON body and signature headers.
  </Step>

  <Step title="Your endpoint verifies the signature">
    See [Verifying signatures](/get-started/webhooks/signature-verification). Reject any delivery that fails verification.
  </Step>

  <Step title="Your endpoint responds 2xx fast">
    Respond `200 OK` within **10 seconds**. Do the real work (database writes, downstream calls, email sends) on a background queue.
  </Step>

  <Step title="On non-2xx or timeout, BlooBank retries">
    Following the retry policy. Your endpoint must be idempotent — see [Best practices](/get-started/webhooks/best-practices).
  </Step>
</Steps>

## The envelope

Every webhook is a JSON object with the same envelope shape:

```json theme={"dark"}
{
  "event":        "payment_order.success",
  "messageId":    "msg_8H2kPqR9zT4xVm",
  "occurredAt":   "2026-01-15T10:31:24.512Z",
  "deliveryAttempt": 1,
  "data": {
    "id":             "ord_3KpFvBwYzNqMxA7eHbRdJ",
    "kind":           "Payment.Order",
    "status":         "SUCCESS",
    "direction":      "IN",
    "amount":         25000,
    "currency":       "BRL",
    "idempotencyKey": "invoice-2026-0184",
    "instrument": {
      "type":       "PIX_CASH_IN_EMV_DYNAMIC",
      "endToEndId": "E60746948202601151030001A1B2C3D"
    },
    "processedAt": "2026-01-15T10:31:24.512Z"
  }
}
```

| Field             | Description                                                                                                     |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `event`           | Event name (e.g., `payment_order.success`). See [Payment events](/get-started/webhooks/payment-events).         |
| `messageId`       | Unique delivery identifier. Use for deduplication — see [Best practices](/get-started/webhooks/best-practices). |
| `occurredAt`      | UTC ISO 8601 timestamp of the underlying state change (not the delivery attempt).                               |
| `deliveryAttempt` | 1-based counter. `1` on the first try; increments on each redelivery.                                           |
| `data`            | The full resource at the event's state — same shape returned by the corresponding `GET`.                        |

## Delivery headers

BlooBank sends these headers on every webhook:

| Header                                          | Purpose                                                                                   |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `Content-Type: application/json; charset=utf-8` | Always JSON.                                                                              |
| `User-Agent: BlooBank-Webhook/1`                | Identifies the source.                                                                    |
| `X-Bloobank-Event`                              | Event name (mirrors `event` in the body). Useful for routing without parsing JSON.        |
| `X-Bloobank-Message-Id`                         | Mirrors `messageId` in the body.                                                          |
| `X-Bloobank-Signature`                          | The signature — see [Verifying signatures](/get-started/webhooks/signature-verification). |
| `X-Bloobank-Timestamp`                          | Unix epoch in milliseconds of the delivery attempt. Used in signature computation.        |
| `X-Bloobank-Delivery-Attempt`                   | Mirrors `deliveryAttempt` in the body.                                                    |

## Your response

| Response                        | BlooBank behavior                                                                                                     |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `2xx` within 10 s               | Delivery considered successful. No retry.                                                                             |
| `4xx`                           | Delivery considered **permanently failed**. No retry. The event remains visible in the Dashboard for manual replay.   |
| `5xx`                           | Delivery considered **transiently failed**. Retried per the [retry policy](/get-started/webhooks/retry-and-delivery). |
| No response within 10 s timeout | Treated as `5xx`. Retried.                                                                                            |
| Network error / TLS failure     | Treated as `5xx`. Retried.                                                                                            |

<Warning>
  **Respond fast.** The 10-second timeout is firm. Push real work onto a background queue and acknowledge the delivery immediately.
</Warning>

## Configuring an endpoint

Each environment supports one or more webhook endpoint URLs, scoped per event class (e.g., one endpoint for payment-order events, another for future event families).

Requirements:

* HTTPS only (HTTP rejected).
* Public address (no `localhost` or RFC 1918 addresses).
* Valid TLS certificate from a publicly trusted CA.

Best practice: configure **distinct endpoints per environment**. Never point a sandbox configuration at your production server.

## Event classes

Today, BlooBank emits one class of webhook events:

| Class             | Events                                                                                      |
| ----------------- | ------------------------------------------------------------------------------------------- |
| `payment_order.*` | `created`, `approved`, `processing`, `success`, `failed`, `canceled`, `expired`, `refunded` |

See [Payment events](/get-started/webhooks/payment-events) for full payloads and per-event semantics.

## Order and duplication

Webhooks are **at-least-once** with **no order guarantees**:

* A single state transition may be delivered more than once. Your consumer must deduplicate by `messageId`.
* Two transitions on the same order may arrive **out of order** (e.g., `success` before `processing`). Treat the `data` payload as the source of truth — the state inside is always the order's actual current state at the moment of delivery.

See [Best practices](/get-started/webhooks/best-practices) for the idempotent consumer pattern.

## Delivery latency

Typical end-to-end latency from state change to webhook arrival:

| Network condition         | Latency       |
| ------------------------- | ------------- |
| Healthy                   | ≤ 1 second    |
| Under load                | A few seconds |
| Provider/network slowdown | Up to minutes |

Webhooks are **eventually consistent** — designed for reconciliation, not for replacing synchronous responses. For the freshest state, you can always `GET` the resource directly; the webhook is the push notification that something interesting happened.

## When delivery fails permanently

If retries exhaust without success, the delivery is marked **failed** and surfaced in the Dashboard. You can:

1. Inspect the delivery payload and your endpoint's response.
2. Trigger a manual replay (after fixing the receiver).
3. Reconcile by polling the resource — webhooks are a convenience layer, not a strict requirement.

## What webhooks do **not** replace

* The signed-request authentication for outbound API calls — separate concern.
* Idempotency on creates — supply `idempotencyKey` regardless of whether you receive a webhook back.
* Periodic reconciliation — webhook delivery is at-least-once; a missed message can occur in extreme failures. Compare local ledger against `GET /paymentOrders` daily.

## Next

<CardGroup cols={3}>
  <Card title="Verifying signatures" icon="signature" href="/get-started/webhooks/signature-verification">
    HMAC-SHA256 scheme — verify every delivery.
  </Card>

  <Card title="Retry & delivery" icon="paper-plane" href="/get-started/webhooks/retry-and-delivery">
    The retry policy in detail.
  </Card>

  <Card title="Payment events" icon="credit-card" href="/get-started/webhooks/payment-events">
    Event catalog and payloads.
  </Card>

  <Card title="Best practices" icon="check-double" href="/get-started/webhooks/best-practices">
    Idempotency, ordering, deduplication.
  </Card>
</CardGroup>
