# Webhook event catalog

Per-event payload shapes for the six Northstake webhook event types, plus the subscription API and delivery semantics.

For event semantics and subscription setup, see [Subscribe to webhooks](../04-guides/11%20Subscribe%20to%20webhooks.md).

## Common envelope

Every event delivered to a webhook URL has the following top-level shape:

```json
{
  "id": "<event_type>_<azure_id>_<event-scoped suffix>",
  "azure_id": "<account ID>",
  "staking_vault_id": "<vault ID>",
  "event_type": "<one of the six event types>",
  "severity": "info" | "warning" | "error",
  "title": "<short human title>",
  "message": "<one-line human message>",
  "payload": { /* event-specific fields, documented per event below */ },
  "created_at": <unix epoch milliseconds>
}
```

| Field              | Type   | Notes                                                     |
| ------------------ | ------ | --------------------------------------------------------- |
| `id`               | string | Stable per logical event; **use this as your dedupe key** |
| `azure_id`         | string | The Northstake account that owns the vault                |
| `staking_vault_id` | string | The vault the event pertains to                           |
| `event_type`       | string | One of the six values below                               |
| `severity`         | string | Currently always `info` for all six event types           |
| `title`            | string | Human-readable label, suitable for notification headers   |
| `message`          | string | One-line human description                                |
| `payload`          | object | Event-specific; documented per type                       |
| `created_at`       | number | Unix epoch in **milliseconds**                            |

## Event catalogue

### `funding_received`

Fires when the vault's Staking Vault contract emits an `EtherFunded` event (any ETH lands in the vault).

| Field       | Notes                                             |
| ----------- | ------------------------------------------------- |
| Source      | Staking Vault contract event (on-chain)           |
| Frequency   | Per funding transaction                           |
| Vault types | All                                               |
| `id` format | `funding_received_<azure_id>_<TX_HASH_UPPERCASE>` |

Payload:

```json
{
  "tx_hash": "0x…",
  "amount_wei": "1000000000000000000",
  "staking_vault_address": "0x…"
}
```

### `validator_activated`

Fires when a validator transitions to `active` on the beacon chain (Ethereum-level activation, not Lido PDG promotion).

| Field       | Notes                                              |
| ----------- | -------------------------------------------------- |
| Source      | Beacon-chain monitor; keyed by `activation_epoch`  |
| Frequency   | Once per validator                                 |
| Vault types | All                                                |
| `id` format | `validator_activated_<azure_id>_<validator_index>` |

Payload:

```json
{
  "validator_index": 1234567,
  "validator_pubkey": "0x…",
  "epoch": 245678
}
```

### `validator_exited`

Fires when a validator exits the beacon chain.

| Field       | Notes                                           |
| ----------- | ----------------------------------------------- |
| Source      | Beacon-chain monitor; keyed by `exit_epoch`     |
| Frequency   | Once per validator                              |
| Vault types | All                                             |
| `id` format | `validator_exited_<azure_id>_<validator_index>` |

Payload:

```json
{
  "validator_index": 1234567,
  "validator_pubkey": "0x…",
  "epoch": 256789
}
```

### `validator_withdrawal_completed`

Fires when a validator's beacon-chain withdrawal sweep completes. Funds have moved from the validator into the vault's withdrawal queue.

| Field       | Notes                                                                 |
| ----------- | --------------------------------------------------------------------- |
| Source      | Beacon-chain withdrawals data                                         |
| Frequency   | Once per withdrawal (a validator may have multiple sweeps)            |
| Vault types | All                                                                   |
| `id` format | `validator_withdrawal_completed_<azure_id>_<validator_index>_<epoch>` |

Payload:

```json
{
  "validator_index": 1234567,
  "validator_pubkey": "0x…",
  "epoch": 256800,
  "amount_eth": "32.045"
}
```

### `withdrawal_claimed`

Fires when a queued withdrawal is claimed by the recipient (Withdrawal Queue `WithdrawalClaimed` event).

| Field       | Notes                                                                   |
| ----------- | ----------------------------------------------------------------------- |
| Source      | Withdrawal Queue contract event (on-chain)                              |
| Frequency   | Per claim                                                               |
| Vault types | All (where withdrawal queue exists, i.e. all Lido v3 vaults)            |
| `id` format | `withdrawal_claimed_<azure_id>_<withdrawal_queue_address>_<request_id>` |

Payload:

```json
{
  "tx_hash": "0x…",
  "request_id": "42",
  "recipient": "0x…",
  "owner": "0x…",
  "amount_eth": "32.0",
  "withdrawal_queue_address": "0x…"
}
```

### `report_update_available`

Fires when the Lido vault hub (`LazyOracle`) publishes a new report timestamp for one of your vaults. Reports update `obligations.feesToSettle` and validate reserve ratios: see [Fee model](../02-concepts/06%20Fees.md).

| Field       | Notes                                                              |
| ----------- | ------------------------------------------------------------------ |
| Source      | Lido `LazyOracle` (on-chain)                                       |
| Frequency   | Per report (typically once per vault per reporting window)         |
| Vault types | All                                                                |
| `id` format | `report_update_available_<azure_id>_<vault_id>_<report_timestamp>` |

Payload:

```json
{
  "calldata": "0x…",
  "report_timestamp": "1716115200"
}
```

The `calldata` is the constructed lazy-oracle calldata for this vault's update, ready to be submitted on-chain.

## Subscription API

Webhook configuration uses standard CRUD endpoints under `/v1/account/webhooks`. See [Subscribe to webhooks](../04-guides/11%20Subscribe%20to%20webhooks.md) for end-to-end usage.

```text
GET    /v1/account/webhooks            # list
POST   /v1/account/webhooks            # create (step-up MFA required)
PATCH  /v1/account/webhooks/{id}       # update (step-up MFA required)
DELETE /v1/account/webhooks/{id}       # delete (step-up MFA required)
```

### `WebhookEntry`

| Field             | Type              | Notes                                                              |
| ----------------- | ----------------- | ------------------------------------------------------------------ |
| `id`              | string            | Server-assigned                                                    |
| `url`             | string            | HTTPS-only                                                         |
| `eventTypes`      | string\[] \| null | One or more of the six event types; `null` = all                   |
| `stakingVaultIds` | string\[] \| null | One or more vault IDs; `null` = all your vaults                    |
| `active`          | boolean           | Whether deliveries are currently active                            |
| `createdAt`       | string            | ISO 8601 timestamp                                                 |
| `secretToken`     | string            | Returned only on `POST` create; persist for signature verification |

## Delivery semantics

* **At-least-once delivery**: your handler must be idempotent (dedupe by `id`)
* **Retries with backoff**: failed deliveries are retried automatically
* **Auto-disable**: webhooks that fail consistently for an extended period may flip to `active: false`
* **Ordering not guaranteed**: events may arrive out of chronological order; use `created_at` for sequencing in your handler

## Related

* [Subscribe to webhooks](../04-guides/11%20Subscribe%20to%20webhooks.md): event semantics and subscription setup

<br />