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

# Webhooks Setup

> Configure webhooks to receive real-time notifications about events in your Crown account

## Access Management Console

Navigate to the **Manage Users** section in your Crown dashboard to begin setting up webhooks.

## Register Your Webhook URL

1. Enter your webhook endpoint URL in the **Webhook URL** field (e.g., `https://your-app.com/webhooks`)
2. Click **Register Webhook**

Your webhook will be immediately active and begin receiving events.

## Webhook Events

Your registered endpoint will receive POST requests for the following events:

* **deposit-processed** - When a deposit has been successfully processed
* **order-completed** - When a currency conversion order completes
* **token-transfer-completed** - When a token transfer finishes
* **withdraw-completed** - When a withdrawal is processed
* **claim-completed** - When a yield claim is completed

See the [Webhooks API Reference](/api-reference/webhooks/deposit-processed) for detailed payload schemas.

## Managing Webhooks

You can manage your registered webhooks directly from the dashboard:

* **Pause** - Temporarily stop receiving webhook events without deleting the registration
* **Remove** - Permanently delete the webhook registration

<Warning>
  Ensure your webhook endpoint returns a `200 OK` response quickly (within 5 seconds) to acknowledge receipt. Failed webhooks may be retried automatically.
</Warning>

## Webhook Security

Every webhook request is signed so you can verify it genuinely came from Crown and was not tampered with in transit. Signatures use **RSA-SHA256** (PKCS#1 v1.5): Crown signs each request with its private key, and you verify it with Crown's public key.

### Public key

Use the following RSA public key to verify the `X-Crown-Signature` on incoming webhooks:

```text theme={null}
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxQGZuqIeWH96kOId6SBD
e4mMTAELlSw81WHt6E+ywk3FUhlUoRukRiwdMwK8laikd+7wdRd0ZTX6M3xwQtNb
6AbvSLKBUgbFJzRD/vc5yCXO92p/mmRMzdDc9whp9iHWwpFgveAAfoiOcllZOKki
iNwHvnhpS2F0Ybxw9HaJqsgQ+g+h0e8lYrTAxlBEcQyE2LAwUDcBGVvXJZNLf5pn
8ftmj5LE/7eF8LfzJQbinQXr0xZXkAGwnuA9hzO8ofyrTH9IKtU9swomTSWwGh4N
FH58pSn4eaIArJuvafnC+vgyRpviWc/rnpSNheGi+gpXEQlwXIRl0gDjfMul0EdO
zwIDAQAB
-----END PUBLIC KEY-----
```

### Signature headers

Each request includes:

| Header                | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `X-Crown-Signature`   | Base64-encoded RSA-SHA256 signature                       |
| `X-Request-Timestamp` | Unix timestamp (seconds) used to build the signed payload |
| `Content-Type`        | `application/json`                                        |

### How the signature is generated

1. Build the signed payload string: `{timestamp}:{body}`, where `timestamp` is the exact value of `X-Request-Timestamp` and `body` is the raw request body.
2. Compute the `SHA256` digest of that payload string.
3. Sign the digest with RSA-SHA256 using Crown's private key.
4. Base64-encode the result — this is the `X-Crown-Signature` value.

<Warning>
  Because step 3 signs the SHA256 **digest** (and RSA-SHA256 applies SHA256 again internally), the signature effectively covers a **double SHA256** of the payload. When verifying, hash the payload once yourself and pass that digest to the RSA-SHA256 verifier — do not pass the raw payload. This is the most common cause of verification failures.
</Warning>

### Verifying the signature

Reconstruct the payload from the request you received, compute its SHA256 digest, and verify the signature against Crown's public key. Use the **raw request body exactly as received** — do not re-serialize the JSON, as any difference in spacing or key order will break verification.

<CodeGroup>
  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  function verifySignature(signature, timestamp, rawBody, publicKeyPem) {
    const payload = `${timestamp}:${rawBody}`;
    const hash = crypto.createHash('sha256').update(payload).digest();

    const verifier = crypto.createVerify('RSA-SHA256');
    verifier.update(hash); // pass the digest, not the raw payload
    verifier.end();

    return verifier.verify(publicKeyPem, signature, 'base64');
  }

  // Usage (Express, with the raw body preserved)
  const isValid = verifySignature(
    req.headers['x-crown-signature'],
    req.headers['x-request-timestamp'],
    rawBody,
    publicKey,
  );
  ```

  ```python Python theme={null}
  import hashlib
  import base64
  from cryptography.hazmat.primitives import hashes
  from cryptography.hazmat.primitives.asymmetric import padding

  def verify_signature(signature, timestamp, raw_body, public_key):
      payload = f"{timestamp}:{raw_body}"
      payload_hash = hashlib.sha256(payload.encode()).digest()
      try:
          public_key.verify(
              base64.b64decode(signature),
              payload_hash,  # re-hashed by verify() -> effective double SHA256
              padding.PKCS1v15(),
              hashes.SHA256(),
          )
          return True
      except Exception:
          return False
  ```
</CodeGroup>

### Replay protection

Also validate `X-Request-Timestamp`: reject requests whose timestamp is older than \~5 minutes to guard against replay attacks. Always serve your webhook endpoint over HTTPS.
