# Belvo Webhooks

Belvo (open finance for Latin America) sends webhooks for Payments (Mexico and Brazil) and
Aggregation. Payloads are **not signed**. The only authentication is an optional static
credential, sent in the `Authorization` header, that you set on the webhook.

## When to Use This Skill

* How do I receive Belvo webhooks?
* How do I verify the Belvo `Authorization` header (Bearer or Basic)?
* Does Belvo sign webhook payloads? (No: shared credential header only)
* Why does my Belvo webhook endpoint return 401?
* How do I handle `payment_request_successful`, `consent_confirmed` or `historical_update` events?

## Verification (core)

In the Belvo portal you can add an authentication value to each webhook. Belvo then sends it as
`Authorization` on every request. On Payments Mexico webhooks you enter a token and Belvo sends
`Authorization: Bearer <token>` (Payments Brazil calls its field "an optional bearer token" but
doesn't show the header, so confirm it with a test delivery); on Aggregation webhooks you enter the full value
(`Bearer <token>` or `Basic <base64 username:password>`) and Belvo sends it unchanged. Store the
**whole header value, scheme prefix included**, and compare it in full. Don't strip or parse the prefix.

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyBelvoRequest(headers, expected = process.env.BELVO_AUTHORIZATION) {
  if (!expected) return false;                 // never accept when unconfigured
  const received = headers['authorization'];   // full value, e.g. "Bearer 1234-..."
  return Boolean(received) && safeEqual(received, expected);
}

```

Return `401` when this fails, `400` for a body that isn't a JSON object, and `200` once handled.
Read the body raw (`express.raw({ type: '*/*' })`) and parse JSON only after the check passes.
Python and Next.js equivalents are in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/belvo-webhooks/references/verification.md).

> **A static shared credential authenticates the sender, not the body.** There is no signature and
> no timestamp, so a captured request can be replayed and the body can't be proven untampered.
> Use HTTPS only, rotate the credential if it leaks, and make handlers idempotent.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/belvo-webhooks/examples/express/),
> [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/belvo-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/belvo-webhooks/examples/fastapi/).

## Event Shapes

Each product has its own body shape; tell them apart by the fields that are present:

| Product | Shape | Examples |
| --- | --- | --- |
| Payments Mexico | `eventType`, `eventCode`, `datetime`, `details` | `payment_request_successful`, `payment_request_failed`, `payment_request_chargeback`, `consent_submitted`, `consent_confirmed`, `consent_incomplete_information`, `consent_rejected`, `payment_method_registration_successful`, `payment_method_registration_failed`, `payment_method_registration_canceled`, `customer_blocked`, `customer_unblocked`, `withdrawal_created` |
| Payments Brazil, schema V2 | `schema_version`, `resource`, `resource_id`, `resource_version`, `timestamp` | `resource`: `BANK_ACCOUNT`, `CHARGE`, `CUSTOMER`, `PAYMENT_AUTHORIZATION` |
| Payments Brazil, schema V1 | `webhook_id`, `webhook_type`, `webhook_code`, `object_id`, `external_id`, `data` | `CHARGES`, `ENROLLMENTS` and `PAYMENT_INTENTS` with `STATUS_UPDATE`; `CUSTOMERS` and `TRANSACTIONS` with `OBJECT_CREATED` |
| Aggregation | `webhook_id`, `webhook_type`, `webhook_code`, `link_id`, `data` | `historical_update`, `new_accounts_available`, `new_transactions_available`, `transactions_updated`, `transactions_deleted`, `invalid_credentials`, `token_required`, `link_deleted` |

Brazil sends V2 only for resources created with the `X-Belvo-API-Resource-Version: Payments-BR.V2`
header, and V1 otherwise. Both Brazil schemas only identify the resource, so fetch it from
`GET /payments/br/{resource}/{id}/`. Brazil V1 and Aggregation share `webhook_type`/`webhook_code`:
Brazil V1 carries `object_id`, Aggregation carries `link_id`. Aggregation `webhook_type` is the
resource (`ACCOUNTS`, `TRANSACTIONS`, `LINK`, `CONSENT`, ...); see
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/belvo-webhooks/references/overview.md) for the full lists.

## Retries

Payments Mexico retries only on unreachable, timeout or `5xx` responses (every 60 minutes, 3
attempts in total) and **never retries `4xx`**. A `401` from a wrong credential is therefore
dropped for good. Payments Brazil retries up to three times, 60 seconds apart, when it gets no
`2xx`. Aggregation retries five times with linear backoff when it gets no `2xx`.

## Environment Variables

```bash
BELVO_AUTHORIZATION=Bearer 00000000-0000-0000-0000-000000000000   # full header value

```

## Local Development

```bash
npx hookdeck-cli listen 3000 belvo --path /webhooks/belvo

```

No account required. Use the printed URL as the webhook URL in the Belvo portal.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/belvo-webhooks/references/overview.md) - Payments Mexico, Payments Brazil (V1/V2) and Aggregation shapes, event codes, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/belvo-webhooks/references/setup.md) - Creating a webhook and setting its authentication value
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/belvo-webhooks/references/verification.md) - Header check in Node, Python and Next.js, gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/belvo-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests