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

POST/v1/tenants/:slug/webhooks

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

POST
/v1/tenants/:slug/webhooks
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"
    }
  }
}

GET/v1/tenants/:slug/webhooks

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, or failed.

  • 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

GET
/v1/tenants/:slug/webhooks
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
    }
  }
}

GET/v1/tenants/:slug/webhooks/:delivery_id

Retrieve webhook delivery

Get detailed information about a specific webhook delivery, including the full payload and response details.

Request

GET
/v1/tenants/:slug/webhooks/:delivery_id
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"
    }
  }
}

POST/v1/tenants/:slug/webhooks/:delivery_id/retry

Retry webhook delivery

Manually retry a failed webhook delivery. Useful for recovering from temporary endpoint issues or after fixing webhook endpoint problems.

Request

POST
/v1/tenants/:slug/webhooks/:delivery_id/retry
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"
  }
}

POST/v1/tenants/:slug/webhooks/test

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

POST
/v1/tenants/:slug/webhooks/test
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

  1. Always verify signatures before processing webhook data
  2. Use HTTPS endpoints for webhook URLs
  3. Handle idempotency - events may be delivered more than once
  4. Respond quickly - return 200 status within 30 seconds
  5. 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.

Was this page helpful?