Webhooks Guide

Overview

Webhooks allow you to receive real-time notifications about events happening in your Kira integration. Instead of polling the API, events are pushed to your server immediately when they occur.


Register Your Webhook URL

Register your webhook endpoint with the Kira API:

Endpoint

POST /webhooks/register

Request

curl -X POST https://api.balampay.com/webhooks/register \
  -H "Authorization: Bearer GENERATED_TOKEN" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://your-domain.com/webhooks/kira",
    "secret": "your_webhook_secret",
    "client_uuid": "your-client-uuid"
  }'

Parameters

ParameterTypeRequiredDescription
webhook_urlstringYesYour HTTPS endpoint that will receive webhooks (must be HTTPS in production)
secretstringYesSecret key used to sign webhook payloads for verification
client_uuidstringYesYour unique client identifier

Response

{
  "message": "Webhook registered successfully"
}

Webhook Structure

All webhooks follow the same base structure:

{
  "event": "event.name",
  "data": {
    ...
  }
}
FieldTypeDescription
eventstringThe event type (e.g., user.created, virtual_account.deposit_funds_received)
dataobjectEvent-specific payload

Your endpoint must return a 200 status code to acknowledge receipt. Non-2xx responses will be treated as a delivery failure.


Signature Verification

All webhooks include an x-signature-sha256 header containing an HMAC SHA256 signature computed with the secret you provided during registration. Always verify this signature before processing webhooks.

Verification Steps

  1. Extract the x-signature-sha256 header from the request
  2. Compute the HMAC SHA256 of the JSON-stringified request body using your secret
  3. Compare the computed signature with the received signature using a timing-safe comparison

Example (Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature) {
  const secret = process.env.KIRA_WEBHOOK_SECRET;

  const computedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(computedSignature)
  );
}

Important: If you use middleware that parses the JSON body (e.g., express.json()), you must JSON.stringify() the parsed body to verify the signature — do not use the raw buffer.


Event Catalog

User Events

EventDescription
user.createdUser created successfully
user.updatedUser profile updated
user.verification.acceptedIdentity verification (KYC/KYB) approved
user.verification.failedIdentity verification (KYC/KYB) rejected

user.created

Sent when a new user is created via POST /v1/users.

{
  "event": "user.created",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440001",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "individual",
    "email": "[email protected]",
    "phone": "+1234567890",
    "status": "unverified",
    "verification_status": "not_started",
    "verification_mode": "automatic",
    "created_at": "2024-01-15T10:30:00Z",
    "verification_triggered": true
  }
}

user.updated

Sent when a user is updated via PUT /v1/users/{userId}.

{
  "event": "user.updated",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440002",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "individual",
    "email": "[email protected]",
    "phone": "+1234567890",
    "status": "verified",
    "verification_status": "verified",
    "updated_at": "2024-01-15T11:00:00Z",
    "updated_fields": {},
    "requires_reverification": false,
    "verification_triggered": false
  }
}

user.verification.accepted

Sent when identity verification (KYC/KYB) is approved.

{
  "event": "user.verification.accepted",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440003",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "id": "ver_550e8400-e29b-41d4-a716-446655440010",
    "verification_status": "verified",
    "kyc_type": "full"
  }
}

user.verification.failed

Sent when identity verification is rejected. Includes an array of reasons for the rejection.

{
  "event": "user.verification.failed",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440004",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "id": "ver_550e8400-e29b-41d4-a716-446655440010",
    "verification_status": "rejected",
    "reasons": ["Document expired", "Address mismatch"]
  }
}

Payment Link Events

EventDescription
card_paymentCard payment completed on a payment link
barcode_generatedCash payment barcode generated or status updated

card_payment

Sent when a card payment is completed on a payment link.

{
  "event": "card_payment",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440020",
    "payment_link_id": "550e8400-e29b-41d4-a716-446655440030",
    "client_uuid": "550e8400-e29b-41d4-a716-446655440040",
    "reference": "YOUR_INTERNAL_REFERENCE",
    "status": "completed"
  }
}

barcode_generated

Sent when a cash payment barcode is generated or its status changes.

{
  "event": "barcode_generated",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440021",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipient_id": "550e8400-e29b-41d4-a716-446655440050",
    "base_amount": "100.00",
    "base_currency": "USD",
    "quote_currency": "MXN",
    "cash_request_id": "550e8400-e29b-41d4-a716-446655440060",
    "cash_request_status": "DEPOSITED",
    "client_uuid": "550e8400-e29b-41d4-a716-446655440040",
    "payment_link_id": "550e8400-e29b-41d4-a716-446655440030",
    "referenceid": "YOUR_INTERNAL_REFERENCE",
    "metadata": {
      "quote_id": "550e8400-e29b-41d4-a716-446655440070",
      "quote_amount": "1850.00",
      "fixed_fee": "3.99",
      "rate": "18.50"
    }
  }
}

Virtual Account & Payout Events

For virtual account deposit events, payout events, and settlement events, see the Virtual Account Webhooks Guide.


Best Practices

  1. Return 200 quickly - Acknowledge receipt immediately, process the event asynchronously
  2. Verify signatures - Always validate webhook authenticity before processing
  3. Implement idempotency - Use unique identifiers from the data payload (e.g., event_id) to prevent duplicate processing
  4. Use HTTPS - Webhook URLs must use HTTPS in production
  5. Store secrets securely - Use environment variables, never commit secrets to code

Testing

Local Development with ngrok

# 1. Start your local server
npm run dev

# 2. In another terminal, start ngrok
ngrok http 3000

# 3. Register the ngrok URL as your webhook endpoint
curl -X POST https://api.balampay.com/webhooks/register \
  -H "Authorization: Bearer GENERATED_TOKEN" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://abc123.ngrok.io/webhooks/kira",
    "secret": "dev_secret_123",
    "client_uuid": "YOUR_CLIENT_UUID"
  }'

Sandbox

In sandbox mode, user verification is automatically approved and webhooks are sent immediately for user creation and verification events.


Support