Webhook payload reference

Billixi sends signed JSON payloads to your HTTPS endpoint when checkout outcomes occur. Use this guide to verify signatures, parse events, and integrate with CRM, fulfillment, or automation tools.

Event-driven

Subscribe to payment.* events per endpoint.

Signed payloads

HMAC-SHA256 via Billixi-Signature header.

Test from UI

Send test events with livemode: false before going live.

Setup

Configure endpoints

Create and manage webhooks in Preferences → Webhooks . You need the Webhooks permission (workspace owners always have access).

  1. Add an HTTPS endpoint URL on your server.
  2. Select one or more event types (payment.completed, etc.).
  3. Copy the signing secret (whsec_…) when shown — it may not be displayed again.
  4. Use Send test event on each webhook card to validate your handler.

HTTP delivery

Request

  • Method: POST
  • Content-Type: application/json
  • Body: UTF-8 JSON (see event payloads)
  • Timeout: respond within 30s; slow endpoints may be retried

Expected response

  • Return any 2xx status to acknowledge receipt.
  • Non-2xx responses are treated as delivery failures (logged in Billixi).
  • Process asynchronously — verify signature first, then queue work.

Security

Verify signatures

Compute HMAC-SHA256 over the raw request body (bytes as received). Compare to the Billixi-Signature header (lowercase hex). Secrets prefixed with whsec_ use the base64url-decoded key material after the prefix.

import crypto from 'node:crypto'

export function verifyBillixiWebhook(rawBody, signatureHeader, secret) {
  const key = secret.startsWith('whsec_') ? decodeWhsec(secret) : Buffer.from(secret, 'utf8')
  const expected = crypto.createHmac('sha256', key).update(rawBody, 'utf8').digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader.toLowerCase()))
}

function decodeWhsec(secret) {
  const encoded = secret.slice('whsec_'.length).replace(/-/g, '+').replace(/_/g, '/')
  return Buffer.from(encoded, 'base64')
}

// Express: use express.raw({ type: 'application/json' }) on the webhook route

Request headers

POST /your-endpoint HTTP/1.1
Host: api.yourcompany.com
Content-Type: application/json
Billixi-Signature: a1b2c3d4e5f6…
X-Webhook-Timestamp: 1716472800
HeaderDescription
Billixi-SignatureHMAC-SHA256 hex digest of raw body — primary verification header.
X-Webhook-SignatureSame value as Billixi-Signature (compatibility alias).
X-Webhook-TimestampUnix timestamp (seconds) when the request was sent.

Event payloads

All events share the same top-level envelope. Event-specific data lives under data.

payment.completed

Sale completed

Fired when a buyer successfully pays. Affiliate attribution is included in this event — there is no separate affiliate.sale webhook.

Seller event

Fields

FieldType
data.paymentIntentId
Required
uuid
data.productId
Required
uuid
data.productNamestring
data.amount
Required
number
data.currency
Required
string
data.network
Required
string
data.saleType
Required
"direct" | "via_promoter"
data.affiliateLinkIduuid | null
data.promoterUserIdstring | null
data.platformFee
Required
number
data.affiliateFee
Required
number
data.sellerAmount
Required
number

Example payload

{
  "id": "evt_test_37c4a3b1",
  "type": "payment.completed",
  "createdAt": "2026-09-15T23:55:50.053Z",
  "livemode": true,
  "data": {
    "paymentIntentId": "b15334b5-bf79-4acb-a08e-5579f9a433f3",
    "productId": "3d11f1eb-1802-43e9-8156-68350176e3db",
    "productName": "Sample digital product",
    "amount": 49,
    "currency": "USDT",
    "network": "Polygon",
    "saleType": "direct",
    "affiliateLinkId": null,
    "promoterUserId": null,
    "platformFee": 1.23,
    "affiliateFee": 0,
    "sellerAmount": 47.77
  }
}

livemode is true in this example.

payment.failed

Checkout failed

Fired when checkout does not complete (expired, rejected, or on-chain failure).

Seller event

Fields

FieldType
data.paymentIntentId
Required
uuid
data.productId
Required
uuid
data.amount
Required
number
data.currency
Required
string
data.reason
Required
string

Example payload

{
  "id": "evt_test_807a7f1f",
  "type": "payment.failed",
  "createdAt": "2026-09-15T23:55:50.053Z",
  "livemode": true,
  "data": {
    "paymentIntentId": "3ebeae8f-00ec-479b-8a6e-6b8d945fac4f",
    "productId": "cb7fbeeb-bf41-42c2-acd0-a649e3120695",
    "amount": 49,
    "currency": "USDT",
    "reason": "checkout_expired"
  }
}

livemode is true in this example.

payment.refunded

Refund processed

Fired when a completed sale is refunded to the buyer.

Seller event

Fields

FieldType
data.paymentIntentId
Required
uuid
data.productId
Required
uuid
data.amount
Required
number
data.currency
Required
string
data.refundAmount
Required
number

Example payload

{
  "id": "evt_test_a213efed",
  "type": "payment.refunded",
  "createdAt": "2026-09-15T23:55:50.053Z",
  "livemode": true,
  "data": {
    "paymentIntentId": "3d2983fe-f8f2-4186-af51-4f8b45e362ad",
    "productId": "e79d0e6a-5715-45e1-bdea-c0aa6dacf2a2",
    "amount": 49,
    "currency": "USDT",
    "refundAmount": 49
  }
}

livemode is true in this example.

Shared envelope

Every webhook body includes these top-level fields before event-specific data.

FieldTypeDescription
idstringUnique event id (evt_…). Use for idempotent processing.
typestringEvent key, e.g. payment.completed — matches your subscription.
createdAtstring (ISO 8601)UTC timestamp when Billixi emitted the event.
livemodebooleanfalse for test deliveries from Preferences; true for real checkout events.
dataobjectEvent-specific payload — see each event below.

Management API

Configure webhooks via the dashboard, or use the Workspace API with OAuth2 (workspaces:write + Webhooks permission). Test delivery uses POST …/webhooks/{'{webhookId}'}/test.

GET
/api/v1/workspace/workspaces/{workspaceId}/preferences/webhooks

List webhooks (preferences)

OAuth2workspaces:read

Parameters

workspaceId
Required
uuid

The workspace ID

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:read'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/preferences/webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer ${access_token}',
  },
});

const data = await response.json();
console.log(data);
POST
/api/v1/workspace/workspaces/{workspaceId}/webhooks

Create webhook

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

Request Body

{
  "name": "My Webhook",
  "url": "https://example.com/webhook",
  "events": [
    "payment.completed",
    "payment.failed",
    "payment.refunded"
  ]
}

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ${access_token}',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "My Webhook",
    "url": "https://example.com/webhook",
    "events": [
        "payment.completed",
        "payment.failed",
        "payment.refunded"
    ]
}),
});

const data = await response.json();
console.log(data);
PUT
/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}

Update webhook

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

webhookId
Required
uuid

Webhook ID

Request Body

{
  "name": "My Webhook",
  "url": "https://example.com/webhook",
  "events": [
    "payment.completed"
  ]
}

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}', {
  method: 'PUT',
  headers: {
    'Authorization': 'Bearer ${access_token}',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "My Webhook",
    "url": "https://example.com/webhook",
    "events": [
        "payment.completed"
    ]
}),
});

const data = await response.json();
console.log(data);
PUT
/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/status

Update webhook status

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

webhookId
Required
uuid

Webhook ID

Request Body

{
  "status": "active"
}

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/status', {
  method: 'PUT',
  headers: {
    'Authorization': 'Bearer ${access_token}',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "status": "active"
}),
});

const data = await response.json();
console.log(data);
POST
/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/test

Send test webhook event

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

webhookId
Required
uuid

Webhook ID

Request Body

{
  "event": "payment.completed"
}

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/test', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ${access_token}',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "event": "payment.completed"
}),
});

const data = await response.json();
console.log(data);
POST
/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/regenerate-secret

Regenerate webhook signing secret

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

webhookId
Required
uuid

Webhook ID

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}/regenerate-secret', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ${access_token}',
    'Content-Type': 'application/json',
  },
});

const data = await response.json();
console.log(data);
DELETE
/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}

Delete webhook

OAuth2workspaces:write

Parameters

workspaceId
Required
uuid

The workspace ID

webhookId
Required
uuid

Webhook ID

Code example

// Get OAuth2 token
const tokenResponse = await fetch('https://auth.billixi.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'workspaces:write'
  })
});
const { access_token } = await tokenResponse.json();

// Make API request
const response = await fetch('/api/gateway/api/v1/workspace/workspaces/{workspaceId}/webhooks/{webhookId}', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer ${access_token}',
  },
});

const data = await response.json();
console.log(data);

REST API reference for catalog, payments, and OAuth2: Billixi API docs