Webhooks
Receive real-time notifications when events occur in your ledger. Built with enterprise-grade reliability including automatic retries, HMAC signature verification, and delivery tracking.
Overview
Webhooks provide instant notifications when important events happen in your ledger, such as transaction posting or balance updates. Perfect for building real-time applications, triggering automated processes, and maintaining data synchronization.
Supported events
- transaction.posted - A transaction has been successfully posted
- balance.updated - An account balance has changed
- account.created - A new account has been created
- account.updated - An account has been modified
Delivery guarantees
- Reliable delivery with exponential backoff retry (3 attempts)
- HMAC signature verification for security
- Automatic retry on failure (1min → 2min → 4min)
- Dead letter queue for permanently failed deliveries
Configure webhook endpoint
Configure webhook delivery for your tenant. Only one webhook endpoint per tenant is supported currently.
Required attributes
- Name
url- Type
- string
- Description
HTTPS URL where webhooks will be delivered.
- Name
events- Type
- array
- Description
Array of event types to subscribe to.
Optional attributes
- Name
secret- Type
- string
- Description
Secret key for HMAC signature (auto-generated if not provided).
- Name
enabled- Type
- boolean
- Description
Whether webhook delivery is enabled (default: true).
Request
curl -X POST https://api.sandbox.whocomply.com/v1/tenants/acme-fintech/webhooks \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.yourapp.com/webhooks",
"events": ["transaction.posted", "balance.updated"],
"enabled": true
}'
Response
{
"success": true,
"data": {
"webhook_config": {
"url": "https://api.yourapp.com/webhooks",
"secret": "whsec_1234567890abcdef1234567890abcdef",
"events": ["transaction.posted", "balance.updated"],
"enabled": true,
"created_at": "2025-09-26T10:30:00Z"
}
}
}
List webhook deliveries
View webhook delivery history, status, and configuration. Perfect for monitoring webhook health and debugging delivery issues.
Optional attributes
- Name
limit- Type
- integer
- Description
Number of deliveries to return (default: 50, max: 100).
- Name
offset- Type
- integer
- Description
Number of deliveries to skip for pagination.
- Name
event_type- Type
- string
- Description
Filter by event type.
- Name
status- Type
- string
- Description
Filter by delivery status:
pending,delivered, orfailed.
- Name
start_date- Type
- string
- Description
Filter deliveries after this date (YYYY-MM-DD format).
- Name
end_date- Type
- string
- Description
Filter deliveries before this date (YYYY-MM-DD format).
Request
curl -G https://api.sandbox.whocomply.com/v1/tenants/acme-fintech/webhooks \
-H "Authorization: Bearer {api_key}" \
-d limit=20 \
-d event_type=transaction.posted \
-d status=delivered
Response
{
"success": true,
"data": {
"webhook_config": {
"url": "https://api.yourapp.com/webhooks",
"events": ["transaction.posted", "balance.updated"],
"enabled": true
},
"deliveries": [
{
"id": "wh_delivery_01JB2M3N4P5Q6R7S8T9U0V",
"event_id": "evt_01JB2M3N4P5Q6R7S8T9U0W",
"event_type": "transaction.posted",
"status": "delivered",
"status_code": 200,
"attempts": 1,
"max_attempts": 3,
"delivered_at": "2025-09-26T10:31:00Z",
"created_at": "2025-09-26T10:30:00Z"
},
{
"id": "wh_delivery_01JB2M3N4P5Q6R7S8T9U0X",
"event_id": "evt_01JB2M3N4P5Q6R7S8T9U0Y",
"event_type": "balance.updated",
"status": "failed",
"status_code": 500,
"attempts": 3,
"max_attempts": 3,
"next_retry_at": null,
"failed_at": "2025-09-26T10:35:00Z",
"created_at": "2025-09-26T10:30:00Z"
}
],
"pagination": {
"total": 2,
"limit": 20,
"offset": 0,
"has_more": false
}
}
}
Retrieve webhook delivery
Get detailed information about a specific webhook delivery, including the full payload and response details.
Request
curl https://api.sandbox.whocomply.com/v1/tenants/acme-fintech/webhooks/wh_delivery_01JB2M3N4P5Q6R7S8T9U0V \
-H "Authorization: Bearer {api_key}"
Response
{
"success": true,
"data": {
"delivery": {
"id": "wh_delivery_01JB2M3N4P5Q6R7S8T9U0V",
"event_id": "evt_01JB2M3N4P5Q6R7S8T9U0W",
"event_type": "transaction.posted",
"status": "delivered",
"status_code": 200,
"attempts": 1,
"max_attempts": 3,
"payload": {
"id": "evt_01JB2M3N4P5Q6R7S8T9U0W",
"type": "transaction.posted",
"created": 1727348400,
"data": {
"transaction_id": "txn_01JB2M3N4P5Q6R7S8T9U0Y",
"idempotency_key": "bank_funding_001",
"description": "Customer funding from GTBank",
"amount": "10000.00",
"currency": "NGN"
},
"tenant_id": "tenant_01JB2M3N4P5Q6R7S8T9U0Z",
"livemode": true
},
"response_body": "OK",
"delivered_at": "2025-09-26T10:31:00Z",
"created_at": "2025-09-26T10:30:00Z"
}
}
}
Retry webhook delivery
Manually retry a failed webhook delivery. Useful for recovering from temporary endpoint issues or after fixing webhook endpoint problems.
Request
curl -X POST https://api.sandbox.whocomply.com/v1/tenants/acme-fintech/webhooks/wh_delivery_01JB2M3N4P5Q6R7S8T9U0X/retry \
-H "Authorization: Bearer {api_key}"
Response
{
"success": true,
"data": {
"message": "Webhook delivery queued for retry",
"delivery_id": "wh_delivery_01JB2M3N4P5Q6R7S8T9U0X",
"attempts": 4,
"next_retry_at": "2025-09-26T10:35:00Z"
}
}
Test webhook configuration
Send a test webhook to verify your endpoint configuration. Perfect for testing during integration and verifying webhook endpoint health.
Optional attributes
- Name
event_type- Type
- string
- Description
Event type for the test webhook (default: "test.webhook").
Request
curl -X POST https://api.sandbox.whocomply.com/v1/tenants/acme-fintech/webhooks/test \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"event_type": "test.webhook"
}'
Response
{
"success": true,
"data": {
"delivery_id": "wh_delivery_test_01JB2M3N4P5Q6R7S8T9U12",
"status": "delivered",
"status_code": 200,
"response_time_ms": 156,
"test_payload": {
"id": "evt_test_01JB2M3N4P5Q6R7S8T9U13",
"type": "test.webhook",
"created": 1727348500,
"data": {
"message": "This is a test webhook from Ledger Service"
},
"tenant_id": "tenant_01JB2M3N4P5Q6R7S8T9U0Z",
"livemode": true
}
}
}
Webhook payload format
All webhook payloads follow this standard format:
{
"id": "evt_01JB2M3N4P5Q6R7S8T9U0W",
"type": "transaction.posted",
"created": 1727348400,
"data": {
// Event-specific data
},
"tenant_id": "tenant_01JB2M3N4P5Q6R7S8T9U0Z",
"livemode": true
}
Common attributes
- Name
id- Type
- string
- Description
Unique event identifier.
- Name
type- Type
- string
- Description
Event type (transaction.posted, balance.updated, etc.).
- Name
created- Type
- integer
- Description
Unix timestamp when the event occurred.
- Name
data- Type
- object
- Description
Event-specific payload data.
- Name
tenant_id- Type
- string
- Description
Your tenant identifier.
- Name
livemode- Type
- boolean
- Description
Whether this is a production event.
Event-specific payloads
{
"id": "evt_01JB2M3N4P5Q6R7S8T9U0W",
"type": "transaction.posted",
"created": 1727348400,
"data": {
"transaction_id": "txn_01JB2M3N4P5Q6R7S8T9U0Y",
"idempotency_key": "bank_funding_001",
"description": "Customer funding from GTBank",
"reference": "BANK_TXN_12345",
"currency": "NGN",
"lines": [
{
"account_id": "acc_01JB2M3N4P5Q6R7S8T9U10",
"account_code": "1101",
"amount": "10000.00",
"side": "debit"
},
{
"account_id": "acc_01JB2M3N4P5Q6R7S8T9U0X",
"account_code": "2001",
"amount": "10000.00",
"side": "credit"
}
],
"metadata": {
"customer_id": "cust_123"
}
},
"tenant_id": "tenant_01JB2M3N4P5Q6R7S8T9U0Z",
"livemode": true
}
Security & Verification
HMAC signature verification
Every webhook includes an HMAC-SHA256 signature in the X-Webhook-Signature header:
X-Webhook-Signature: sha256=abc123def456...
Verify the signature to ensure webhook authenticity:
const crypto = require('crypto')
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex')
const receivedSignature = signature.replace('sha256=', '')
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex'),
)
}
// Usage
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
req.headers['x-webhook-signature'],
'whsec_1234567890abcdef1234567890abcdef',
)
Best practices
- Always verify signatures before processing webhook data
- Use HTTPS endpoints for webhook URLs
- Handle idempotency - events may be delivered more than once
- Respond quickly - return 200 status within 30 seconds
- Log webhook events for debugging and audit purposes
Retry logic
Webhook delivery follows automatic retry with exponential backoff:
- Attempt 1: Immediate delivery
- Attempt 2: 1 minute later
- Attempt 3: 2 minutes later
- Final: Marked as permanently failed
Success criteria
A webhook delivery is considered successful if:
- HTTP status code 200-299
- Response received within 30 seconds
Rate limits
- Webhook configuration: 10 requests per minute
- Manual retries: 5 retries per webhook per hour
- Test webhooks: 20 tests per hour
Error responses
Based on the actual webhook handler implementation, errors return simple messages:
Invalid JSON payload
{
"success": false,
"error": "invalid JSON payload"
}
Validation error
{
"success": false,
"error": "Request validation failed",
"details": {
"field": "url",
"tag": "required",
"value": ""
}
}
Invalid delivery ID
{
"success": false,
"error": "Invalid delivery ID"
}
Webhook delivery not found
{
"success": false,
"error": "Webhook delivery not found"
}
Test webhook failed
{
"success": false,
"error": "Test webhook failed"
}
Service-level errors
{
"success": false,
"error": "tenant not found"
}
Note: Error messages come directly from the service layer and may vary based on the specific failure condition.