---
title: Webhooks
description: Receive real-time events for every BlindPay update across customers, bank accounts, payouts, payins, and transfers.
---

## What it is

A webhook is an HTTP callback that delivers BlindPay events in real time. For every customer created, bank account created, and payout event, you receive the full data as it happens.

## How it works

These are all the events you can receive:

| Event                  | Description                                                         |
| ---------------------- | ------------------------------------------------------------------- |
| bankAccount.new        | Triggered when a bank account is created                            |
| blockchainWallet.new   | Triggered when a blockchain wallet is created                       |
| limitIncrease.new      | Triggered when a limit increase request is requested                |
| limitIncrease.update   | Triggered when a limit increase request is updated                  |
| payin.new              | Triggered when a payin is started                                   |
| payin.update           | Triggered when a payin receives an update                           |
| payin.complete         | Triggered when a payin is completed or failed                       |
| payin.partnerFee       | Triggered when a payin is completed and a partner fee is delivered  |
| payout.new             | Triggered when a payout is started                                  |
| payout.update          | Triggered when a payout receives an update                          |
| payout.complete        | Triggered when a payout is completed, failed or refunded            |
| payout.partnerFee      | Triggered when a payout is completed and a partner fee is delivered |
| customer.new           | Triggered when a customer is created                                |
| customer.update        | Triggered when a customer is updated                                |
| customer.delete        | Triggered when a customer is deleted                                |
| tos.accept             | Triggered when a terms of service is accepted                       |
| transfer.new           | Triggered when a stablecoin [transfer](/docs/essentials/transfers) is started                     |
| transfer.update        | Triggered when a stablecoin [transfer](/docs/essentials/transfers) receives an update             |
| transfer.complete      | Triggered when a stablecoin [transfer](/docs/essentials/transfers) is completed or failed         |
| virtualAccount.new     | Triggered when a virtual account is created                         |
| virtualAccount.complete| Triggered when a virtual account setup is completed                 |
| wallet.new             | Triggered when a [wallet](/docs/essentials/wallets) is created                                  |
| wallet.inbound         | Triggered when a stablecoin deposit is received in a [wallet](/docs/essentials/wallets)         |

## Prerequisites

::c-prerequisites
::

## Create a webhook

Go to the [BlindPay Dashboard](https://app.blindpay.com/){target="\_blank"}, select an instance and click on the `Webhooks` tab.

If you just want to check if the events are being triggered, you can use [Webhook Cool](https://webhook.cool/), a testing tool which will provide you an unique URL to receive the events.

![BlindPay Webhooks](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/webhooks.jpg)

If you want to check all the events triggered by BlindPay or retry some event that you want to receive again, you can go to `Events dashboard`.

![BlindPay Webhook Debugging](https://pub-4fabf5dd55154f19a0384b16f2b816d9.r2.dev/webhook_debug.jpg)

## Verify a webhook

Each webhook call includes verification headers to ensure the request is authentic and hasn't been tampered with.

### Headers

| Header           | Description                                             |
| ---------------- | ------------------------------------------------------- |
| `svix-id`        | Unique message identifier (same when webhook is resent) |
| `svix-timestamp` | Timestamp in seconds since epoch                        |
| `svix-signature` | Base64 encoded list of signatures (space delimited)     |

### Verification process

Construct the signed content by concatenating the id, timestamp, and payload:

```javascript
const signedContent = `${svix_id}.${svix_timestamp}.${body}`
```

Calculate the expected signature using HMAC-SHA256:

```javascript
const crypto = require('node:crypto')

// Extract the base64 portion of your signing secret (after whsec_ prefix)

const secretBytes = require('node:buffer').Buffer.from(secret.split('_')[1], 'base64')

const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64')
```

Compare signatures from the `svix-signature` header:

- The header contains space-delimited signatures with version prefixes
- Example: `v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=`
- Remove the version prefix (e.g., `v1,`) before comparing
- Use constant-time string comparison to prevent timing attacks

Verify timestamp to prevent replay attacks:

- Compare `svix-timestamp` against your system time
- Ensure it's within your tolerance window

### Example verification

::c-alert{icon="circle-info" variant="warning"}
To get your `secret` you need to go to BlindPay Dashboard > Open your instance > Webhooks > Click on the ellipsis button and click on `Get secret`.
::

```javascript
const crypto = require('node:crypto')

// Example values
const secret = 'whsec_plJ3nmyCDGBKInavdOK15jsl'
const payload = '{"event_type":"ping","data":{"success":true}}'
const msg_id = 'msg_loFOjxBNrRLzqYUf'
const timestamp = '1731705121'

// Expected signature: v1,rAvfW3dJ/X/qxhsaXPOyyCGmRKsaKWcsNccKXlIktD0=
const signedContent = `${msg_id}.${timestamp}.${payload}`

const secretBytes = require('node:buffer').Buffer.from(secret.split('_')[1], 'base64')

const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64')

console.log(`v1,${signature}`)
```

:::warning
Never modify the request body before verification, as even small changes will invalidate the signature.
:::

## Related

- [Customers](/docs/essentials/customers) · [Transfers](/docs/essentials/transfers) · [Wallets](/docs/essentials/wallets)
- [Partner Fees](/docs/essentials/partner-fees)
