Subscriptions (Webhooks)
Subscriptions allow you to receive real-time notifications of events that occur within your platform instance.
Endpoints
GET
/subscriptions
Get subscriptions
POST
/subscriptions
Create subscription
DELETE
/subscriptions/{id}
Delete subscription
GET
/subscriptions
Retrieve your webhook callback subscriptions.
Response
200 OK
{
"data": [
{
"id": "sub_123456789",
"url": "https://api.yourapp.com/webhooks/bultra",
"events": [
"APPLICATION_STATUS_CHANGED",
"PAYMENT_EXECUTED",
"WITHDRAWAL_CHANGES_REQUESTED"
],
"active": true,
"created_at": "2026-01-15T10:30:00Z"
}
]
}
POST
/subscriptions
Register a new webhook subscription.
Request Body
| Field | Type | Description |
|---|---|---|
url required |
string | The webhook URL to receive events |
events required |
array | List of event types to subscribe to |
secret |
string | Shared secret for webhook signature verification |
Example Request
Request
{
"url": "https://api.yourapp.com/webhooks/bultra",
"events": [
"APPLICATION_STATUS_CHANGED",
"CUSTOMER_STATUS_CHANGED",
"PAYMENT_EXECUTED",
"PAYMENT_CHANGES_REQUESTED",
"WITHDRAWAL_EXECUTED",
"WITHDRAWAL_CHANGES_REQUESTED",
"TRANSFER_EXECUTED",
"EXCHANGE_EXECUTED"
],
"secret": "your_webhook_secret_key"
}
Response
201 Created
{
"id": "sub_123456789",
"url": "https://api.yourapp.com/webhooks/bultra",
"events": [
"APPLICATION_STATUS_CHANGED",
"CUSTOMER_STATUS_CHANGED",
"PAYMENT_EXECUTED",
"PAYMENT_CHANGES_REQUESTED",
"WITHDRAWAL_EXECUTED",
"WITHDRAWAL_CHANGES_REQUESTED",
"TRANSFER_EXECUTED",
"EXCHANGE_EXECUTED"
],
"active": true,
"created_at": "2026-01-15T10:30:00Z"
}
Important: Production webhook URLs must be explicitly whitelisted by Bultra before receiving callbacks. Contact your implementation engineer or account manager for approval.
DELETE
/subscriptions/{id}
Remove an existing webhook subscription.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id required |
string | The subscription ID |
Response
204 No Content
// No response body
Event Structure
All webhook events follow a consistent format:
Event Payload
{
"event_id": "evt_550e8400-e29b-41d4-a716-446655440000",
"event_date": "2026-01-15T10:30:00Z",
"event_type": "PAYMENT_EXECUTED",
"event_data": {
"payment_id": "pay_999888777",
"account_id": "acc_123456789",
"amount": "5000.00",
"currency": "USD",
"status": "EXECUTED"
}
}
Event Fields
| Field | Description |
|---|---|
event_id |
Unique, idempotent UUID that remains unchanged during event replays |
event_date |
Timestamp indicating when the event was generated |
event_type |
Classification of the event |
event_data |
Business information relevant to the event |
Available Event Types
Application Events
| Event Type | Description |
|---|---|
APPLICATION_STATUS_CHANGED |
Application status has changed |
APPLICATION_APPROVED |
Application was approved |
APPLICATION_REJECTED |
Application was rejected |
Customer Events
| Event Type | Description |
|---|---|
CUSTOMER_STATUS_CHANGED |
Customer status has changed |
CUSTOMER_CREATED |
New customer created from approved application |
Payment Events
| Event Type | Description |
|---|---|
PAYMENT_EXECUTED |
Payment completed successfully |
PAYMENT_CHANGES_REQUESTED |
RFI triggered for payment |
PAYMENT_FAILED |
Payment failed |
Withdrawal Events
| Event Type | Description |
|---|---|
WITHDRAWAL_EXECUTED |
Withdrawal completed successfully |
WITHDRAWAL_CHANGES_REQUESTED |
RFI triggered for withdrawal |
WITHDRAWAL_FAILED |
Withdrawal failed |
Transfer Events
| Event Type | Description |
|---|---|
TRANSFER_EXECUTED |
Transfer completed successfully |
TRANSFER_FAILED |
Transfer failed |
Exchange Events
| Event Type | Description |
|---|---|
EXCHANGE_EXECUTED |
Exchange completed successfully |
EXCHANGE_FAILED |
Exchange failed (e.g., slippage exceeded) |
Webhook Security
To verify that webhook requests are coming from Bultra, check the signature in the X-Bultra-Signature header:
Signature Verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Best Practices
- Respond quickly: Return a 200 response within 5 seconds
- Process asynchronously: Queue events for background processing
- Handle idempotency: Use
event_idto deduplicate events - Verify signatures: Always verify webhook signatures in production
- Monitor failures: Track and alert on webhook delivery failures