Webhooks

Kashia sends webhook notifications to your server when events occur. Configure your webhook URL in Dashboard → Integrations. All webhooks are signed with your webhook secret for verification.

Failed deliveries are retried up to 3 times with exponential backoff.

Webhook payload format

json
{
  "event": "escrow.completed",
  "data": {
    "id": "escrow-uuid",
    "reference": "ESC-a1b2c3d4",
    "status": "completed",
    "amount": 100000000,
    "currency": "NGN",
    "platform_fee": 1500000,
    "merchant_fee": 10000000,
    "total_fees": 11500000,
    "total_amount": 111500000,
    "seller_amount": 100000000,
    "buyer": { "id": "...", "email": "..." },
    "seller": { "id": "...", "email": "..." },
    "metadata": { "order_id": "ORD-12345" }
  },
  "timestamp": "2025-01-15T10:30:00Z",
  "webhook_id": "webhook-event-uuid"
}

Escrow events include fee breakdown fields (amounts in kobo):

FieldDescription
amountBase transaction amount — what the seller receives on release
platform_feeKashia platform fee charged to the buyer
merchant_feeMarketplace commission credited to the merchant (0 for single-vendor)
total_feesSum of platform_fee + merchant_fee
total_amountTotal paid by the buyer (amount + total_fees)
seller_amountAmount released to the seller — equals amount

Signature verification

Kashia signs every webhook with your webhook secret using HMAC-SHA256. The signature is sent in the X-Kashia-Signature header.

Header
X-Kashia-Signature: sha256=5d5b9e7c8b...

Verification examples

JavaScript
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expectedSignature = 'sha256=' +
    crypto.createHmac('sha256', secret)
      .update(JSON.stringify(payload))
      .digest('hex');

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

"code-hl-comment">// In your webhook handler
app.post('/webhooks/kashia', (req, res) => {
  const signature = req.headers['x-kashia-signature'];

  if (!verifyWebhook(req.body, signature, process.env.KASHIA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;

  switch (event) {
    case 'escrow.active':
      "code-hl-comment">// Payment received — update your order status
      break;
    case 'escrow.completed':
      "code-hl-comment">// Delivery confirmed — funds released
      break;
    case 'escrow.disputed':
      "code-hl-comment">// Dispute opened — notify relevant parties
      break;
  }

  res.status(200).send('OK');
});

Event types

EventTriggered When
payment_link.createdA new payment link is created
payment_link.expiredA payment link expires
escrow.createdAn escrow is created (payment pending)
escrow.activePayment received, funds locked
escrow.awaiting_confirmationSeller marked as delivered — includes shipping_details in payload when provided
escrow.completedBuyer confirmed, funds released
escrow.refundedFull refund issued
escrow.cancelledEscrow cancelled
escrow.disputedDispute opened
dispute.openedA new dispute is opened
dispute.escalatedDispute escalated to Kashia admin
dispute.resolvedDispute resolved
withdrawal.initiatedWithdrawal accepted and processing with Monnify
withdrawal.successfulFunds sent to the user's bank account
withdrawal.failedWithdrawal failed — funds returned to available balance
bank_account.addedUser added a verified bank account

Shipping on awaiting confirmation

When a seller marks an escrow as delivered, the escrow.awaiting_confirmation webhook includes a delivery object with tracking, carrier, dispatch contact, notes, and image metadata when provided.

json
{
  "event": "escrow.awaiting_confirmation",
  "data": {
    "id": "escrow-uuid",
    "reference": "ESC-a1b2c3d4",
    "status": "awaiting_confirmation",
    "delivery": {
      "shipped_at": "2025-01-15T10:30:00Z",
      "tracking_number": "TRK-123456",
      "carrier": "DHL",
      "carrier_website": "https://dhl.com/track/TRK-123456",
      "dispatch_name": "John Doe",
      "dispatch_phone": "+2348000000000",
      "estimated_delivery_date": "2025-01-20",
      "notes": "Left with reception"
    }
  }
}

Withdrawal webhook example

json
{
  "event": "withdrawal.successful",
  "data": {
    "withdrawal_id": "uuid",
    "reference": "WTH-a1b2c3d4",
    "amount": 9500000,
    "net_amount": 9495000,
    "status": "successful",
    "user_id": "user-uuid"
  },
  "timestamp": "2025-01-15T10:35:00Z",
  "webhook_id": "webhook-event-uuid"
}

Retry policy

  • First retry: 1 minute after failure
  • Second retry: 5 minutes after first retry
  • Third retry: 30 minutes after second retry
  • After 3 failures: webhook marked as failed

Best practices

  • Always return 200 OK quickly — process webhook data asynchronously
  • Use the webhook_id to deduplicate (you may receive the same event more than once)
  • Verify signatures before processing
  • Store webhook payloads for debugging