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 Settings → Webhooks 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 Settings → Webhooks
  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