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

# Webhooks

> Receive real-time notifications when events happen in your Bluvo project

## Overview

Bluvo webhooks notify your application in real-time when events occur — wallets connecting, balances updating, withdrawals completing, and more. Instead of polling the API, your server receives an HTTP POST with a signed JSON payload the moment something happens.

Webhooks are managed through the [Bluvo Portal](https://portal.bluvo.dev). There is no REST API for webhook management — all configuration (creating endpoints, selecting events, rotating secrets) is done in the dashboard.

## Supported events

| Event                                                                 | Description                                   |
| --------------------------------------------------------------------- | --------------------------------------------- |
| [`wallet.created`](/webhooks/events/wallet-created)                   | A wallet was connected via OAuth or API key   |
| [`wallet.balance.updated`](/webhooks/events/wallet-balance-updated)   | Balances were synced for a wallet             |
| [`wallet.deleted`](/webhooks/events/wallet-deleted)                   | A wallet was removed                          |
| [`quotation.created`](/webhooks/events/quotation-created)             | A withdrawal quote was successfully generated |
| [`quotation.error`](/webhooks/events/quotation-error)                 | A withdrawal quote failed                     |
| [`transaction.created`](/webhooks/events/transaction-created)         | A withdrawal was submitted to the exchange    |
| [`transaction.error`](/webhooks/events/transaction-error)             | A withdrawal failed                           |
| [`transaction.broadcasted`](/webhooks/events/transaction-broadcasted) | A withdrawal was confirmed on-chain           |
| [`error.fatal`](/webhooks/events/error-fatal)                         | An unrecoverable error occurred               |

## Creating a webhook endpoint

<Steps>
  <Step title="Open the Webhooks page">
    In the [Bluvo Portal](https://portal.bluvo.dev), navigate to **Settings > Webhooks**.
  </Step>

  <Step title="Add an endpoint">
    Click **Add Endpoint**, enter the URL of your receiver, and select the events you want to subscribe to.
  </Step>

  <Step title="Copy your signing secret">
    After creation, copy the webhook signing secret. You'll need this to [verify signatures](/webhooks/verify-signatures). Store it securely — it won't be shown again.
  </Step>
</Steps>

<Info>
  You can create up to **3 webhook endpoints** per project.
</Info>

## Payload structure

Every webhook delivery sends a POST request with a JSON body following this envelope:

```json theme={null}
{
  "event": "wallet.created",
  "eventId": "wal_abc123",
  "timestamp": "2026-04-21T12:00:00.000Z",
  "data": {
    // Event-specific fields
  }
}
```

| Field       | Type     | Description                                         |
| ----------- | -------- | --------------------------------------------------- |
| `event`     | `string` | The event type (e.g., `wallet.created`)             |
| `eventId`   | `string` | Unique identifier for this event                    |
| `timestamp` | `string` | ISO 8601 timestamp of when the event occurred       |
| `data`      | `object` | Event-specific payload — see individual event pages |

### Headers

Each delivery includes two signature headers:

| Header                | Description                          |
| --------------------- | ------------------------------------ |
| `X-Webhook-Signature` | Base64-encoded HMAC-SHA256 signature |
| `X-Webhook-Timestamp` | Unix timestamp in milliseconds       |

## Building a receiver

Here's a minimal Express.js receiver that verifies the signature and processes events:

```typescript theme={null}
import express from "express";
import crypto from "node:crypto";

const app = express();
const WEBHOOK_SECRET = process.env.BLUVO_WEBHOOK_SECRET!;

app.post("/webhooks/bluvo", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-webhook-signature"] as string;
  const timestamp = req.headers["x-webhook-timestamp"] as string;

  // Verify signature
  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}\n${req.body}`)
    .digest("base64");

  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );

  if (!isValid) {
    return res.status(401).send("Invalid signature");
  }

  // Return 200 immediately, process async
  res.status(200).send("OK");

  const event = JSON.parse(req.body.toString());
  // Handle event asynchronously...
});

app.listen(3000);
```

<Warning>
  Always use `express.raw()` or equivalent to access the raw request body for signature verification. Parsing the body as JSON first will alter the string and break the signature check.
</Warning>

## Idempotency

Use the `eventId` field to deduplicate deliveries. The same event may be delivered more than once (e.g., if your server returns a timeout before Bluvo receives the 200 response). Your receiver should be idempotent — processing the same `eventId` twice should have no side effects.

## Next steps

<CardGroup cols={3}>
  <Card title="Verify Signatures" icon="shield-check" href="/webhooks/verify-signatures">
    Secure your endpoint with HMAC-SHA256 verification.
  </Card>

  <Card title="Retries & Logs" icon="rotate" href="/webhooks/retries-and-logs">
    Understand the retry schedule and view delivery logs.
  </Card>

  <Card title="Event Reference" icon="list" href="/webhooks/events/wallet-created">
    Browse detailed payload schemas for every event.
  </Card>
</CardGroup>
