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

# Webhooks

Webhooks let Melio notify your system when something changes, so you don't have to poll. When a subscribed event occurs, Melio sends an HTTP `POST` to the endpoint you registered.

Each partner has a **single** webhook endpoint. You manage it through the `/webhook` resource and choose which event types it receives.

## Managing your endpoint

| Method   | Path              | Description                                                                                    |
| -------- | ----------------- | ---------------------------------------------------------------------------------------------- |
| `GET`    | `/webhook`        | Retrieve your endpoint and the events it's subscribed to. Returns `404` if none is configured. |
| `PATCH`  | `/webhook`        | Create or update your endpoint.                                                                |
| `DELETE` | `/webhook`        | Remove your endpoint. Melio stops sending deliveries.                                          |
| `GET`    | `/webhook/events` | List the event types you can subscribe to.                                                     |

### Creating or updating

`PATCH /webhook` is a partial update: only the fields you send are changed.

* `url` and `events` are **required the first time** (when no endpoint exists yet).
* Sending `events` **replaces** the entire subscription list, so always include the full set you want (at least one).
* Set `isActive: false` to pause deliveries without deleting the endpoint, and `true` to resume.

```bash theme={null}
curl -X PATCH https://api.example.com/v2/webhook \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://partner.example.com/webhooks/melio",
    "description": "Production endpoint",
    "events": ["api.entity.created", "api.account.updated", "api.payment.updated"]
  }'
```

The endpoint `url` must be an `https` URL.

## Event types

| Event                    | Fires when                                                           |
| ------------------------ | -------------------------------------------------------------------- |
| `api.entity.created`     | A business entity is created.                                        |
| `api.entity.updated`     | An entity changes, including when a risk decision is reached.        |
| `api.account.created`    | An account (internal or external) is created.                        |
| `api.account.updated`    | An account changes, including when it becomes verified.              |
| `api.account.deleted`    | An account is deleted.                                               |
| `api.payment.created`    | A payment is created.                                                |
| `api.payment.updated`    | A payment changes (a status transition, an edit, or a cancellation). |
| `api.limitation.updated` | A business's limitations change.                                     |

## Delivery payload

Each delivery is a **lightweight notification**. It identifies the affected resource, not its full state. Treat the payload as a signal to fetch the current resource (for example `GET /payments/{id}`) rather than as the source of truth, so you never act on a stale snapshot.

Every delivery includes these fields:

| Field              | Description                                                                                                    |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `event`            | The event type (see above).                                                                                    |
| `messageId`        | Unique event id, stable across retries. Use it for idempotency. Also sent as the `X-Melio-Delivery-Id` header. |
| `id`               | The id of the affected resource (`ent_...`, `acct_...`, or `pay_...`).                                         |
| `entityId`         | The business entity that holds the resource.                                                                   |
| `occurredAt`       | When the change occurred (RFC 3339, UTC). See [Timestamps](/docs/timestamps).                                       |
| `externalId`       | Your own id for the affected resource, if one was set. See [External IDs](/docs/external-ids).                      |
| `entityExternalId` | Your own id for the holding entity, if one was set (present on account and payment events).                    |
| `metadata`         | The affected resource's [metadata](/docs/metadata), if any was set.                                                 |

Some events carry a small `data` object; most do not, and you should fetch the resource for its current state. Account created/deleted events include:

```json theme={null}
{
  "event": "api.account.created",
  "messageId": "evt_...",
  "id": "acct_...",
  "entityId": "ent_...",
  "occurredAt": "2026-07-08T15:04:05Z",
  "data": {
    "ownershipType": "external",
    "accountType": "ach"
  }
}
```

## Verifying deliveries

Every delivery is signed so you can confirm it genuinely came from Melio and was not tampered with. The raw request body is signed with **HMAC-SHA256** (hex encoded) using your **API key secret**, and the signature is sent in the `X-Melio-Signature` header.

To verify, compute the HMAC over the **raw** request body (before any JSON parsing) and compare it to the header using a constant-time comparison:

```js theme={null}
import crypto from 'crypto';

function isValidSignature(rawBody, signatureHeader, apiKeySecret) {
  const expected = crypto
    .createHmac('sha256', apiKeySecret)
    .update(rawBody) // the raw bytes, not the parsed object
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}
```

<Warning>
  Verify the signature against the raw request body exactly as received. Re-serializing the parsed JSON can change byte-for-byte formatting and produce a different HMAC, causing valid deliveries to fail verification. Reject any delivery whose signature does not match.
</Warning>

## Handling deliveries reliably

* **Be idempotent.** The same event may be delivered more than once. Deduplicate on `messageId` (also available as the `X-Melio-Delivery-Id` header) so you process each event only once.
* **Respond quickly with a `2xx`.** Acknowledge receipt fast and do heavy work asynchronously. Failed deliveries are retried.
* **Fetch current state.** Because deliveries are notifications, always read the resource (`GET`) to get its latest state before acting, rather than trusting a possibly out-of-order payload.
* **Pause safely.** Set `isActive: false` to stop deliveries during maintenance instead of tearing down and rebuilding your subscription.

## Related

* [Timestamps](/docs/timestamps) - the `occurredAt` format.
* [External IDs](/docs/external-ids) - correlating events to your own records.
* [Metadata](/docs/metadata) - extra context carried on deliveries.
