Webhooks

Receive events from all BlindPay updates.

What are Webhooks?

Webhooks are a way to receive events from all BlindPay updates. This means that for every receiver created, bank account created and every payout event, you will receive all the data in real time.

These are all the events that you can receive:

EventDescription
bankAccount.newTriggered when a bank account is created
receiver.newTriggered when a receiver is created
receiver.updateTriggered when a receiver is updated
payout.newTriggered when a payout is started
payout.updateTriggered when a payout receive an update
payout.completeTriggered when a payout is completed, failed or refunded
payout.partnerFeeTriggered when a payout is completed and a partner fee is delivered
payin.newTriggered when a payin is started
payin.updateTriggered when a payin receive an update
payin.completeTriggered when a payin is completed or failed
payin.partnerFeeTriggered when a payin is completed and a partner fee is delivered

Creating a webhook

Before creating a payout, you need to:

  1. Create an account on BlindPay
  2. Create a development instance

Now you can go to the BlindPay Dashboard, 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, a testing tool which will provide you an unique URL to receive the events.

BlindPay Webhooks

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

Verifying webhooks

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

Headers

HeaderDescription
svix-idUnique message identifier (same when webhook is resent)
svix-timestampTimestamp in seconds since epoch
svix-signatureBase64 encoded list of signatures (space delimited)

Verification Process

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

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

Calculate the expected signature using HMAC-SHA256:

const crypto = require('crypto');

// Extract the base64 portion of your signing secret (after whsec_ prefix)
const secretBytes = 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

const crypto = require('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 = Buffer.from(secret.split('_')[1], "base64");
const signature = crypto
  .createHmac('sha256', secretBytes)
  .update(signedContent)
  .digest('base64');

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

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