Skip to content

Webhooks

Webhooks allow your application to receive real-time notifications when events occur in your Cashfin account.

Overview

Instead of polling the API for updates, webhooks push data to your application when:

  • A payment is completed
  • An order status changes
  • A subscription is created or updated
  • And more...

Setting Up Webhooks

Step 1: Create a Webhook Endpoint

Create an endpoint on your server to receive webhook events:

javascript
// Express.js example
app.post("/webhooks/cashfin", express.json(), (req, res) => {
  const event = req.body;

  // Process the webhook event
  switch (event.type) {
    case "payment.completed":
      handlePaymentCompleted(event.data);
      break;
    case "order.created":
      handleOrderCreated(event.data);
      break;
    // ... handle other events
  }

  // Acknowledge receipt
  res.status(200).json({ received: true });
});

Step 2: Register Your Webhook

  1. Go to SettingsWebhooks in your dashboard
  2. Click Add Endpoint
  3. Enter your webhook URL (must be HTTPS)
  4. Select the events you want to receive
  5. Save the configuration

Step 3: Verify Webhook Signatures

Verify that webhooks are genuinely from Cashfin:

javascript
const crypto = require("crypto");

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

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

app.post(
  "/webhooks/cashfin",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-cashfin-signature"];
    const webhookSecret = process.env.CASHFIN_WEBHOOK_SECRET;

    if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const event = JSON.parse(req.body);
    // Process event...

    res.status(200).json({ received: true });
  }
);

Webhook Events

Payment Events

EventDescription
payment.initiatedPayment process started
payment.completedPayment successfully completed
payment.failedPayment attempt failed
payment.refundedPayment was refunded

Order Events

EventDescription
order.createdNew order created
order.processingOrder is being processed
order.completedOrder fulfilled
order.cancelledOrder was cancelled

Subscription Events

EventDescription
subscription.createdNew subscription created
subscription.activatedSubscription became active
subscription.renewedSubscription renewed
subscription.cancelledSubscription cancelled
subscription.expiredSubscription expired

Customer Events

EventDescription
customer.createdNew customer created
customer.updatedCustomer information updated

Event Payload Structure

All webhook events follow this structure:

json
{
  "id": "evt_507f1f77bcf86cd799439011",
  "type": "payment.completed",
  "created": "2024-01-15T10:30:00.000Z",
  "data": {
    "id": "pay_507f191e810c19729de860ea",
    "amount": 5000.0,
    "currency": "KES",
    "status": "completed"
    // ... event-specific data
  },
  "business_id": "507f1f77bcf86cd799439011"
}

Event Fields

FieldDescription
idUnique event identifier
typeEvent type (e.g., payment.completed)
createdISO 8601 timestamp of when the event occurred
dataEvent-specific payload
business_idYour business ID

Example Payloads

Payment Completed

json
{
  "id": "evt_abc123",
  "type": "payment.completed",
  "created": "2024-01-15T10:30:00.000Z",
  "data": {
    "id": "pay_xyz789",
    "transaction_id": "txn_123456",
    "amount": 5000.0,
    "currency": "KES",
    "payment_method": "mpesa",
    "customer": {
      "id": "cust_456",
      "name": "John Doe",
      "phone": "+254712345678"
    },
    "reference": "ORD-001",
    "metadata": {}
  }
}

Order Created

json
{
  "id": "evt_def456",
  "type": "order.created",
  "created": "2024-01-15T11:00:00.000Z",
  "data": {
    "id": "ord_789",
    "order_no": "ORD-2024-001",
    "status": "processing",
    "items": [
      {
        "product_id": "prod_123",
        "title": "Widget Pro",
        "quantity": 2,
        "price": 1500.0
      }
    ],
    "total": 3000.0,
    "customer_email": "[email protected]"
  }
}

Subscription Created

json
{
  "id": "evt_ghi789",
  "type": "subscription.created",
  "created": "2024-01-15T12:00:00.000Z",
  "data": {
    "id": "sub_abc",
    "subscription_no": "SUB-2024-001",
    "status": "trial",
    "billing_cycle": "monthly",
    "amount": 999.0,
    "trial": {
      "start_date": "2024-01-15T00:00:00.000Z",
      "end_date": "2024-01-29T23:59:59.000Z"
    },
    "next_billing_date": "2024-01-30T00:00:00.000Z",
    "customer": {
      "id": "cust_123",
      "name": "Jane Doe",
      "email": "[email protected]"
    }
  }
}

Best Practices

1. Respond Quickly

Return a 200 status as soon as possible. Process webhooks asynchronously:

javascript
app.post("/webhooks/cashfin", (req, res) => {
  // Acknowledge immediately
  res.status(200).json({ received: true });

  // Process asynchronously
  setImmediate(() => {
    processWebhook(req.body);
  });
});

2. Handle Duplicates

Webhooks may be retried. Use the event id to detect duplicates:

javascript
const processedEvents = new Set();

function processWebhook(event) {
  if (processedEvents.has(event.id)) {
    return; // Already processed
  }

  processedEvents.add(event.id);
  // Process the event...
}

3. Verify Signatures

Always verify webhook signatures to ensure authenticity:

javascript
if (!verifySignature(req)) {
  return res.status(401).json({ error: "Invalid signature" });
}

4. Implement Retry Logic

If your endpoint is unavailable, Cashfin will retry:

AttemptDelay
1Immediate
25 minutes
330 minutes
42 hours
524 hours

5. Log All Events

Keep a log of all webhook events for debugging:

javascript
app.post("/webhooks/cashfin", (req, res) => {
  console.log("Webhook received:", {
    id: req.body.id,
    type: req.body.type,
    timestamp: new Date().toISOString(),
  });

  // Process...
});

Testing Webhooks

Using the Dashboard

  1. Go to SettingsWebhooks
  2. Click Send Test Event
  3. Select an event type
  4. Click Send

Local Development

Use tools like ngrok to expose your local server:

bash
# Start ngrok
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/cashfin

Troubleshooting

Webhook Not Received

  1. Check your endpoint is publicly accessible
  2. Verify the URL uses HTTPS
  3. Check server logs for errors
  4. Ensure your server responds within 30 seconds

Invalid Signature

  1. Use the correct webhook secret
  2. Verify you're using the raw request body
  3. Check for encoding issues

Missing Events

  1. Verify the event type is enabled in dashboard
  2. Check webhook logs in the dashboard
  3. Ensure your endpoint returns 200 status

Cashfin Business API Documentation