> ## Documentation Index
> Fetch the complete documentation index at: https://docs.credibill.tech/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration Guide

> Step-by-step guide to integrate CrediBill into your SaaS app

## Overview

This guide walks through integrating CrediBill payment infrastructure into your SaaS application. We'll cover:

1. Setting up payment providers
2. Creating subscriptions
3. Handling webhooks
4. Managing customer payments
5. Monitoring and debugging

## Prerequisites

* Active CrediBill account
* Secret API key from dashboard
* Payment provider account (Flutterwave, PawaPay, Pesapal, or DPO)
* Backend server (Node.js, Python, etc.)
* Basic understanding of webhooks

## 1. Setup

### Store API Key in Environment

```bash theme={null}
# .env.local
CREDIBILL_SECRET_KEY=cb_live_abc123def456
CREDIBILL_WEBHOOK_SECRET=whsec_xyz789
```

### API Base URL

```
https://giant-goldfish-922.convex.site
```

All requests must include your API key in the `Authorization` header:

```bash theme={null}
Authorization: Bearer cb_live_abc123def456
Content-Type: application/json
```

## 2. Configure Payment Providers

### Step 1: Get Provider Credentials

Follow the [Provider Setup Guide](/docs/payments/providers) to obtain credentials for your chosen provider.

### Step 2: Add to CrediBill Dashboard

1. Go to **Settings → Payment Providers**
2. Click **Add Provider**
3. Select provider (Flutterwave, PawaPay, Pesapal, or DPO)
4. Enter credentials obtained from provider
5. Set environment (Test or Live)
6. Click **Save & Test Connection**

### Step 3: Verify Connection

```bash theme={null}
curl -X GET https://giant-goldfish-922.convex.site/api/payment-providers/provider_id_here \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json"
```

Response:

```json theme={null}
{
  "id": "provider_abc123",
  "provider": "flutterwave",
  "connectionStatus": "active",
  "isPrimary": true
}
```

### Multi-Provider Setup (Recommended)

Add providers via API:

```bash theme={null}
# Primary provider (Flutterwave)
curl -X POST https://giant-goldfish-922.convex.site/api/payment-providers \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "flutterwave",
    "publicKey": "pk_test_xyz",
    "secretKey": "cb_test_abc",
    "webhookSecret": "whsec_test_123",
    "environment": "live",
    "isPrimary": true
  }'

# Backup provider (PawaPay)
curl -X POST https://giant-goldfish-922.convex.site/api/payment-providers \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "pawapay",
    "apiToken": "token_test_xyz",
    "apiUrl": "https://api.pawapay.cloud",
    "webhookSecret": "whsec_test_456",
    "environment": "live",
    "isPrimary": false
  }'
```

## 3. Create Customers

### Basic Customer Creation

```bash theme={null}
curl -X POST https://giant-goldfish-922.convex.site/api/customers \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "name": "Acme Corp",
    "metadata": {
      "customerId": "12345",
      "industry": "software"
    }
  }'
```

Response:

```json theme={null}
{
  "id": "cust_abc123def456",
  "email": "customer@example.com",
  "name": "Acme Corp",
  "createdAt": 1703510400
}
```

### With Payment Method

```bash theme={null}
curl -X POST https://giant-goldfish-922.convex.site/api/customers \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "name": "Acme Corp",
    "paymentMethod": "mobile_money_mtn",
    "paymentDetails": {
      "phoneNumber": "+256772123456",
      "provider": "flutterwave"
    }
  }'
```

## 4. Create Subscriptions

### Simple Subscription

```bash theme={null}
curl -X POST https://giant-goldfish-922.convex.site/api/subscriptions \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust_abc123def456",
    "planId": "plan_pro_monthly",
    "trialDays": 14
  }'
```

Response:

```json theme={null}
{
  "id": "sub_abc123def456",
  "customerId": "cust_abc123def456",
  "status": "trialing",
  "trialEndsAt": 1704201600,
  "currentPeriodStart": 1703596800,
  "currentPeriodEnd": 1706188800
}
```

### With Usage-Based Pricing

```bash theme={null}
# Create subscription for plan with usage-based component
curl -X POST https://giant-goldfish-922.convex.site/api/subscriptions \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust_abc123def456",
    "planId": "plan_pro_usage"
  }'

# Report usage as it happens
curl -X POST https://giant-goldfish-922.convex.site/api/usage \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriptionId": "sub_abc123def456",
    "metricName": "api_calls",
    "quantity": 150,
    "timestamp": 1703596800,
    "idempotencyKey": "your-unique-key"
  }'
```

### Subscription Lifecycle

```bash theme={null}
# Retrieve subscription
curl -X GET https://giant-goldfish-922.convex.site/api/subscriptions/sub_abc123 \
  -H "Authorization: Bearer cb_live_abc123def456"

# Update subscription (change plan)
curl -X PATCH https://giant-goldfish-922.convex.site/api/subscriptions/sub_abc123 \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"planId": "plan_enterprise_monthly"}'

# Cancel subscription
curl -X POST https://giant-goldfish-922.convex.site/api/subscriptions/sub_abc123/cancel \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"reason": "customer_request", "immediateEffect": true}'
```

## 5. Handle Webhooks

### Set Up Webhook Endpoint

```typescript theme={null}
import express from "express";
import crypto from "crypto";

const app = express();

// Critical: Use raw body middleware
app.post(
  "/webhooks/credibill",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      // Step 1: Verify signature
      const signature = req.headers["x-credibill-signature"];
      const timestamp = req.headers["x-credibill-timestamp"];
      const rawBody = req.body.toString("utf-8");

      const signaturePayload = `${timestamp}.${rawBody}`;
      const expectedSignature = crypto
        .createHmac("sha256", process.env.CREDIBILL_WEBHOOK_SECRET)
        .update(signaturePayload)
        .digest("hex");

      let isValid = false;
      try {
        isValid = crypto.timingSafeEqual(
          Buffer.from(expectedSignature),
          Buffer.from(signature)
        );
      } catch (err) {
        isValid = false;
      }

      if (!isValid) {
        console.warn("❌ Invalid webhook signature");
        return res.status(401).json({ error: "Invalid signature" });
      }

      // Step 2: Verify timestamp (prevent replay attacks)
      const webhookTime = parseInt(timestamp);
      const now = Date.now();
      const ageMs = now - webhookTime;

      if (ageMs > 5 * 60 * 1000) {
        // 5 minutes
        console.warn("⚠️ Webhook too old:", ageMs);
        return res.status(400).json({ error: "Webhook too old" });
      }

      // Step 3: Acknowledge receipt immediately
      res.status(200).json({ success: true });

      // Step 4: Process webhook asynchronously
      const webhook = JSON.parse(rawBody);
      handleWebhookAsync(webhook).catch((err) => {
        console.error("❌ Webhook processing error:", err);
      });
    } catch (error) {
      console.error("❌ Webhook handling error:", error);
      res.status(500).json({ error: "Internal server error" });
    }
  }
);

async function handleWebhookAsync(webhook) {
  const { event, data } = webhook;

  // Handle idempotency
  const eventId = `${event}:${data.id}:${webhook.timestamp}`;
  const existingEvent = await db.processedEvents.findOne({ eventId });

  if (existingEvent) {
    console.log("✅ Webhook already processed:", eventId);
    return;
  }

  // Process based on event type
  switch (event) {
    case "payment.success":
      await handlePaymentSuccess(data);
      break;

    case "payment.failed":
      await handlePaymentFailed(data);
      break;

    case "subscription.activated":
      await handleSubscriptionActivated(data);
      break;

    case "subscription.past_due":
      await handleSubscriptionPastDue(data);
      break;

    case "subscription.canceled":
      await handleSubscriptionCanceled(data);
      break;

    default:
      console.log("⚠️ Unknown event type:", event);
  }

  // Mark as processed
  await db.processedEvents.create({
    eventId,
    processedAt: new Date(),
  });
}
```

### Handle Payment Success

```typescript theme={null}
async function handlePaymentSuccess(data) {
  const {
    id: transactionId,
    customerId,
    subscriptionId,
    amount,
    currency,
  } = data;

  console.log("✅ Payment successful:", { transactionId, customerId, amount });

  // Update subscription in your database
  await db.subscription.update(subscriptionId, {
    status: "active",
    paidAt: new Date(),
  });

  // Grant feature access
  await enableFeatures(customerId);

  // Send confirmation email
  const customer = await db.customer.get(customerId);
  await sendEmail(customer.email, "payment_success", {
    amount,
    currency,
    date: new Date(),
    invoiceUrl: `https://credibill.tech/invoices/${data.invoiceId}`,
  });

  // Track metric for analytics
  analytics.track("payment_success", {
    customerId,
    subscriptionId,
    amount,
  });
}
```

### Handle Payment Failure

```typescript theme={null}
async function handlePaymentFailed(data) {
  const {
    id: transactionId,
    customerId,
    subscriptionId,
    failureReason,
    attemptNumber,
  } = data;

  console.log("⚠️ Payment failed:", {
    transactionId,
    customerId,
    failureReason,
  });

  // Update subscription to past_due
  await db.subscription.update(subscriptionId, {
    status: "past_due",
    failedAttempts: attemptNumber,
    failedAt: new Date(),
  });

  // Send failure notification
  const customer = await db.customer.get(customerId);
  await sendEmail(customer.email, "payment_failed", {
    reason: failureReason,
    updateLink: `https://your-app.com/settings/payment`,
    retryInfo:
      attemptNumber < 3
        ? "We will retry automatically"
        : "Please update your payment method",
  });

  // Optionally disable access after multiple failures
  if (attemptNumber >= 3) {
    await disableFeatures(customerId);
    await sendEmail(customer.email, "subscription_suspended", {
      reason: "Payment failed after 3 retry attempts",
      resolution: "Update your payment method to reactivate",
    });
  }
}
```

### Handle Subscription Events

```typescript theme={null}
async function handleSubscriptionActivated(data) {
  const { customerId, subscriptionId } = data;

  console.log("✅ Subscription activated:", { subscriptionId, customerId });

  // Mark subscription as active in your system
  await db.subscription.update(subscriptionId, {
    status: "active",
  });

  // Send welcome email
  const customer = await db.customer.get(customerId);
  await sendEmail(customer.email, "welcome", {
    name: customer.name,
    features: ["Feature 1", "Feature 2", "Feature 3"],
  });

  // Start tracking usage if applicable
  await db.usageTracking.create({
    customerId,
    subscriptionId,
    startedAt: new Date(),
  });
}

async function handleSubscriptionPastDue(data) {
  const { customerId, subscriptionId, failedPaymentAttempts } = data;

  console.log("⚠️ Subscription past due:", { subscriptionId, customerId });

  // Show payment failure notice
  await db.notification.create({
    customerId,
    type: "payment_failed",
    message: `Your recent payment failed. Please update your payment method.`,
    actionUrl: `https://your-app.com/settings/payment`,
  });
}

async function handleSubscriptionCanceled(data) {
  const { customerId, subscriptionId, cancelReason } = data;

  console.log("✅ Subscription canceled:", { subscriptionId, cancelReason });

  // Disable features immediately
  await disableFeatures(customerId);

  // Send cancellation confirmation
  const customer = await db.customer.get(customerId);
  await sendEmail(customer.email, "cancellation_confirmed", {
    reason: cancelReason,
    feedback: "We would love to hear your feedback",
  });

  // Archive customer data if applicable
  await archiveCustomerData(customerId);
}
```

## 6. Test Your Integration

### Test Mode

Use provider test credentials to test without real payments. Prefer testing by calling the CrediBill API from your server (App Router) or using cURL.

<Tabs>
  <Tab title="Next.js (App Router)">
    ```ts theme={null}
    // app/api/test/setup/route.ts
    import { NextResponse } from "next/server";

    export async function POST(request: Request) {
      // Sample payload should include provider credentials and test customer details
      const body = await request.json();

      // 1. Add provider
      await fetch("https://api.credibill.io/v1/payment-providers", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(body.provider),
      });

      // 2. Create test customer
      const custRes = await fetch("https://api.credibill.io/v1/customers", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(body.customer),
      });
      const testCustomer = await custRes.json();

      // 3. Create subscription
      const subRes = await fetch("https://api.credibill.io/v1/subscriptions", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ customerId: testCustomer.id, planId: body.planId }),
      });

      const testSubscription = await subRes.json();

      return NextResponse.json({ testCustomer, testSubscription });
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # 1. Configure provider with test credentials
    curl -X POST https://api.credibill.io/v1/payment-providers \
      -H "Authorization: Bearer sk_test_abc" \
      -H "Content-Type: application/json" \
      -d '{"provider":"flutterwave","publicKey":"pk_test_...","secretKey":"sk_test_...","environment":"test","isPrimary":true}'

    # 2. Create test customer
    curl -X POST https://api.credibill.io/v1/customers \
      -H "Authorization: Bearer sk_test_abc" \
      -H "Content-Type: application/json" \
      -d '{"email":"test@example.com","name":"Test Customer"}'

    # 3. Create test subscription
    curl -X POST https://api.credibill.io/v1/subscriptions \
      -H "Authorization: Bearer sk_test_abc" \
      -H "Content-Type: application/json" \
      -d '{"customerId":"cust_test","planId":"plan_pro_monthly"}'
    ```
  </Tab>
</Tabs>

### Test Webhooks Locally

Use ngrok to receive webhooks locally:

```bash theme={null}
# In one terminal: Start your Node app
npm run dev

# In another terminal: Expose to internet
ngrok http 3000
```

Update webhook URL in CrediBill dashboard:

1. Go to **Settings → Webhooks**
2. Update URL to: `https://your-ngrok-url.ngrok.io/webhooks/credibill`
3. Send test webhook from dashboard

### Test Webhook Handling

```typescript theme={null}
// Manually test webhook handler
const testWebhook = {
  event: "payment.success",
  data: {
    id: "txn_test_123",
    customerId: "cust_test_456",
    amount: 50000,
    currency: "UGX",
  },
  timestamp: Date.now(),
};

// Call handler directly
await handleWebhookAsync(testWebhook);
```

## 7. Go Live Checklist

### Provider Setup

* [ ] Live merchant accounts created
* [ ] Business verification completed
* [ ] Live API credentials obtained
* [ ] Webhook URLs configured in provider dashboards
* [ ] Test payment successful with real provider
* [ ] Webhook delivery confirmed

### CrediBill Configuration

* [ ] Primary provider configured in dashboard
* [ ] Backup provider configured (optional but recommended)
* [ ] Environment set to **Live** (not Test)
* [ ] Webhook secret saved securely
* [ ] Webhook URL points to production
* [ ] HTTPS certificate valid (not self-signed)

### Code Review

* [ ] Webhook signature verification implemented
* [ ] Timing-safe comparison used
* [ ] Timestamp validation implemented
* [ ] Idempotency checks in place
* [ ] No credentials hardcoded
* [ ] All secrets in environment variables
* [ ] Error messages don't expose credentials
* [ ] Logging doesn't log sensitive data

### Testing

* [ ] End-to-end payment test successful
* [ ] Failed payment handling tested
* [ ] Webhook delivery tested
* [ ] Signature verification works
* [ ] Idempotency handling works
* [ ] Multiple payment methods tested

### Monitoring

* [ ] Error alerts configured
* [ ] Payment success metrics tracked
* [ ] Webhook delivery monitored
* [ ] Failed payments monitored
* [ ] Unusual activity alerts set up
* [ ] Daily reconciliation process established

### Deployment

* [ ] Code deployed to production
* [ ] Configuration deployed (env vars)
* [ ] Webhooks configured in production
* [ ] First payment tested end-to-end
* [ ] Monitor logs for first 24 hours
* [ ] Team on standby for issues

## 8. Common Integration Issues

### Issue: Webhook Signature Verification Fails

**Cause:** Using parsed JSON body instead of raw body

**Solution:**

```typescript theme={null}
// ❌ Wrong
app.use(express.json()); // This parses body
app.post("/webhook", (req, res) => {
  const body = JSON.stringify(req.body); // Different bytes!
});

// ✅ Correct
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const body = req.body.toString("utf-8"); // Original bytes
});
```

### Issue: Webhooks Not Received

**Checklist:**

* [ ] Webhook URL is publicly accessible (not localhost)
* [ ] HTTPS certificate is valid
* [ ] Firewall allows traffic from CrediBill
* [ ] Endpoint returns 200 OK
* [ ] Endpoint responds within 10 seconds
* [ ] Check webhook logs in CrediBill dashboard

### Issue: Payment Stuck in Pending

**Cause:** Provider API slow or webhook delivery delayed

**Solution:**

* Wait 5-10 minutes (mobile money can be slow)
* Check provider dashboard for payment status
* Check CrediBill webhook logs for delivery attempts
* Contact provider support if stuck > 30 minutes

### Issue: Customer Charged Multiple Times

**Cause:** Duplicate payment initiation or missing idempotency

**Solution:**

* Implement idempotency keys (see code above)
* Check webhook idempotency handling
* Use database unique constraints on transaction IDs
* Check for double form submissions in UI

## Next Steps

* [Webhooks Reference](/docs/webhooks/reference) - Complete webhook event documentation
* [Security Best Practices](/docs/security/best-practices) - Production security guide
* [Provider Setup](/docs/payments/providers) - Detailed provider configuration
* [API Reference](/docs/api-reference/overview) - Full API documentation
