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

# Configure webhooks

> Set up webhook endpoints, subscriptions, and signature validation for event-driven notifications.

<Note>
  Webhooks are enabled by Anchorage Digital upon request. Notify your integration point of contact to enable webhooks for your organization.
</Note>

## Managing webhooks from the dashboard

<Note>
  Step-by-step instructions and screenshots for creating and managing webhooks in the web dashboard are coming soon.
</Note>

For payload schema, signature validation, and code samples for a programmatic setup, see the API instructions below.

## Subscribing to webhooks via API

### API permission group configuration

Update your permission group to include the global permission **Configure webhooks**. Either create a new permission group or edit an existing one.

See [Permission Groups](/knowledge-base/platform/developers/permission-groups) for instructions.

### Webhook validation key

The webhook validation key is a hex-encoded Ed25519 public key used to verify the `Api-Signature` header on incoming notifications. Retrieve it via the API and keep this value tamper-proof.

* [Read webhook validation key](/knowledge-base/api-reference/v2/webhook-notifications/get-webhook-validation-key)

The API returns the key as a hex string (64 characters representing 32 bytes). This is the same format used in the code samples below.

### Idempotency with `message_id`

Each message includes a `message_id` — treat it as an idempotent ID to detect duplicate deliveries. Also use `timestamp` to validate event freshness. For details on retry behavior, timeout requirements, and best practices, see [Delivery and reliability](/knowledge-base/platform/developers/webhooks/webhooks-delivery).

### Webhook schema

| Field        | Description                                                                                         |
| :----------- | :-------------------------------------------------------------------------------------------------- |
| `payload`    | Base64-encoded payload containing the relevant `transactionId` or other IDs depending on event type |
| `timestamp`  | Unix timestamp of when the message was sent                                                         |
| `message_id` | Unique ID for this event message — use as idempotent ID                                             |
| `event_type` | The event topic and name for the subscription                                                       |

```json Schema theme={null}
{
  "payload": "[]byte",
  "timestamp": "int64",
  "message_id": "string",
  "event_type": "string"
}
```

### Create a webhook endpoint

* [Create webhook endpoint](/knowledge-base/api-reference/v2/webhook-notifications/create-webhook-endpoint)
* [List webhook endpoints](/knowledge-base/api-reference/v2/webhook-notifications/list-webhook-endpoint)

### List available event types

List the full set of event types configured for your organization before creating subscriptions.

* [Get webhook event types](/knowledge-base/api-reference/v2/webhook-notifications/list-webhook-event-types)

```json Example response theme={null}
{
  "data": [
    {
      "description": "Notification of a new RIA program customer who has successfully completed and passed Onboarding KYC.",
      "id": "program-customer-onboard.completed"
    },
    {
      "description": "Notification of a successful withdrawal completed on-chain",
      "id": "withdrawal.completed"
    },
    {
      "description": "Notification of a subaccount that currently is low on funds",
      "id": "subaccount.low-balance"
    },
    {
      "description": "Notification of a failed transfer",
      "id": "transfer.failed"
    },
    {
      "description": "Notification of successful completion of deposit attribution",
      "id": "deposit.attributed"
    },
    {
      "description": "Notification of a successful withdrawal initiated but still pending quorum approval and/or risk review",
      "id": "withdrawal.initiated"
    },
    {
      "description": "Notification of a collateral package state change",
      "id": "collateral-package-state-change.collateral-package-state-change"
    },
    {
      "description": "Notification of a successful transfer that is now completed",
      "id": "transfer.completed"
    },
    {
      "description": "Notification of an open RIA program customer process that has requested further information.",
      "id": "program-customer-onboard.rfi"
    },
    {
      "description": "Notification of a new deposit pending attribution",
      "id": "deposit.pending-attribution"
    },
    {
      "description": "Notification of a failed withdrawal",
      "id": "withdrawal.failed"
    },
    {
      "description": "Notification of a subaccount withdrawal request status change",
      "id": "subaccount.subaccount-withdrawal-request-status-change"
    },
    {
      "description": "Notification of a transfer that has successfully been initiated",
      "id": "transfer.initiated"
    },
    {
      "description": "Notification of a subaccount that has successfully completed and passed Onboarding KYC.",
      "id": "subaccount.opened"
    }
  ],
  "page": {
    "next": null
  }
}
```

### Create webhook subscriptions

Subscribe an endpoint to one or more event types. A notification is sent to all subscribed endpoints each time the event fires.

* [Create webhook subscriptions](/knowledge-base/api-reference/v2/webhook-notifications/create-webhook-subscriptions)
* [List webhook subscriptions](/knowledge-base/api-reference/v2/webhook-notifications/list-webhook-endpoint-subscriptions)

### Validate the signature and decode the payload

Every webhook request includes an `Api-Signature` header containing a hex-encoded Ed25519 signature. The signature is computed over the **raw HTTP request body** exactly as received — no timestamp prefix, no additional framing.

To verify:

1. Hex-decode the `Api-Signature` header value to get the raw signature bytes.
2. Verify the signature against the raw request body using the Ed25519 validation key.
3. Base64-decode the `payload` field inside the JSON body to get the event data.

* [Base64 decode tool](https://www.base64decode.org/)
* [Ed25519 signature verification tool](https://cyphr.me/ed25519_tool/ed.html)

<CodeGroup>
  ```javascript JavaScript theme={null}
  import * as ed from '@noble/ed25519';

  const pubKey = 'c14b7f3da18abb17b7304f925a68b18c1ea0dad6663b6b54cb67a737ecb77cd0';

  function toHex(str: string) {
    var result = '';
    for (var i = 0; i < str.length; i++) {
      result += str.charCodeAt(i).toString(16);
    }
    return result;
  }

  const message = `...`; // raw message body
  const signature = '...'; // Api-Signature header value
  const isValid = await ed.verifyAsync(signature, toHex(message), pubKey);

  console.log("Is valid: ", isValid);
  console.log("Payload: ", atob(JSON.parse(message).payload));
  ```

  ```python Python theme={null}
  from http.server import BaseHTTPRequestHandler, HTTPServer
  from nacl.exceptions import BadSignatureError
  from nacl.encoding import HexEncoder
  from nacl.signing import VerifyKey
  import base64
  import json

  publicKey = VerifyKey(
    "c14b7f3da18abb17b7304f925a68b18c1ea0dad6663b6b54cb67a737ecb77cd0",
    encoder=HexEncoder
  )

  class MyServer(BaseHTTPRequestHandler):
    def do_POST(self):
      signature = HexEncoder.decode(self.headers.get("Api-Signature"))
      content_len = int(self.headers.get('Content-Length'))
      message = self.rfile.read(content_len)

      try:
        publicKey.verify(message, signature)
      except BadSignatureError:
        print("Invalid signature")

      payload = base64.b64decode(
        json.loads(message)["payload"].encode('utf-8')
      ).decode('utf-8')
      print("Payload", payload)

      self.send_response(200)
      self.end_headers()
  ```
</CodeGroup>

***

## Payload encryption (optional)

By default, webhook payloads are sent as plaintext JSON signed with the `Api-Signature` header. For an additional layer of security, you can enable ECIES payload encryption by registering a secp256k1 public key with your endpoint.

### How it works

When you register an endpoint with a `public_key`, Anchorage Digital encrypts the entire webhook payload using ECIES (Elliptic Curve Integrated Encryption Scheme) with your secp256k1 public key before delivery. The encrypted request differs from a standard webhook in two ways:

|                 | Standard webhook             | Encrypted webhook             |
| :-------------- | :--------------------------- | :---------------------------- |
| `Content-Type`  | `application/json`           | `application/octet-stream`    |
| Body            | JSON (`WebhookMessage`)      | ECIES ciphertext              |
| `Api-Signature` | Signature over the JSON body | Signature over the ciphertext |

To process an encrypted webhook:

1. Verify the `Api-Signature` against the raw request body (the ciphertext).
2. Decrypt the body using your secp256k1 private key to recover the JSON `WebhookMessage`.
3. Base64-decode the `payload` field inside the decrypted JSON.

### Key format

The `public_key` field must be a **PEM-encoded secp256k1 public key**. This is different from the validation key, which is returned as a hex-encoded Ed25519 key.

| Key                 | Format                                                   | Purpose                        |
| :------------------ | :------------------------------------------------------- | :----------------------------- |
| Validation key      | Hex-encoded Ed25519 (from `GET /webhook/validation-key`) | Verify `Api-Signature` headers |
| Endpoint public key | PEM-encoded secp256k1 (provided by you)                  | Encrypt webhook payloads       |

To enable encryption, include the `public_key` field when [creating](/knowledge-base/api-reference/v2/webhook-notifications/create-webhook-endpoint) or [updating](/knowledge-base/api-reference/v2/webhook-notifications/update-webhook-endpoint) your webhook endpoint. To disable encryption, remove the `public_key` from the endpoint configuration.

***

## Manage endpoints and subscriptions

Once configured, update or cancel webhook endpoints and subscriptions as needed.

* [Get webhook endpoint config](/knowledge-base/api-reference/v2/webhook-notifications/get-webhook-endpoint-config)
* [Update webhook endpoint](/knowledge-base/api-reference/v2/webhook-notifications/update-webhook-endpoint)
* [Cancel webhook subscription](/knowledge-base/api-reference/v2/webhook-notifications/cancel-webhook-subscription)

***

## Additional resources

* [Webhook standards](https://webhooks.fyi/learn-more/standards)
* [Webhook consumer best practices](https://webhooks.fyi/best-practices/webhook-consumers)
