> ## 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.

# Webhooks

> Listen for real-time events from Credibill

Webhooks allow your application to receive real-time notifications when events happen in your Credibill account.

## Overview

Credibill uses webhooks to notify your application when an event happens in your account. Webhooks are particularly useful for asynchronous events like when a Payment Service Provider(Flutterwave, PesaPal, DPO, pawaPay) confirms a payment, a customer disputes a charge, or a recurring payment succeeds.

## Configuring Webhooks

1. Go to the **Webhooks** section in your [Dashboard](https://dashboard.credibill.tech/developers/webhooks). This is found under **Settings → Webhooks**.
2. Click **Add Webhook**.
3. Enter the URL of your server endpoint (e.g., `https://api.mycoolapp.com/webhooks/credibill`).
4. Select the events you want to listen to.

## Event Structure

Every webhook event payload has a consistent structure.

```json theme={null}
{
  "id": "evt_123456",
  "object": "event",
  "type": "invoice.paid",
  "created": 1678900000,
  "data": {
    "object": {
      "id": "in_123456",
      "object": "invoice",
      "amount_due": 1000,
      "status": "paid"
      // ... resource data
    }
  }
}
```

## Verifying Signatures

Credibill signs the webhook events it sends to your endpoints by including a signature in each event's `X-Credibill-Signature` header. This allows you to verify that the events were sent by Credibill, not by a third party.

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

    export const runtime = "nodejs";

    export async function POST(request: Request) {
      const raw = await request.text();
      const signature = request.headers.get("x-credibill-signature") || "";
      const timestamp = request.headers.get("x-credibill-timestamp") || "";
      const webhookSecret = process.env.CREDIBILL_WEBHOOK_SECRET || "";

      // Construct payload and expected signature
      const payload = `${timestamp}.${raw}`;
      const expected = crypto
        .createHmac("sha256", webhookSecret)
        .update(payload)
        .digest("hex");

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

      // Validate signature and timestamp (prevent replay attacks)
      const now = Math.floor(Date.now() / 1000);
      const ts = parseInt(timestamp || "0", 10);
      if (!isValid || Math.abs(now - ts) > 5 * 60) {
        return NextResponse.json(
          { error: "Invalid or stale webhook signature" },
          { status: 401 }
        );
      }

      // Parse event and handle idempotently
      const event = JSON.parse(raw);

      // Idempotency example (pseudo-code): ensure event.id is processed once
      // Example with Prisma:
      // const existing = await prisma.webhookEvent.findUnique({ where: { id: event.id } });
      // if (existing) return NextResponse.json({ received: true });
      // await prisma.webhookEvent.create({ data: { id: event.id, type: event.type, payload: raw } });

      switch (event.type) {
        case "invoice.paid": {
          const invoice = event.data.object;
          // handle the invoice in an idempotent way
          // await fulfillOrder(invoice);
          break;
        }
        default:
          console.log(`Unhandled event type ${event.type}`);
      }

      return NextResponse.json({ received: true });
    }
    ```
  </Tab>

  <Tab title="cURL (simulate webhook)">
    ```bash theme={null}
    # Simulate webhook delivery (compute signature locally)
    export CREDIBILL_WEBHOOK_SECRET="whsec_test_123"
    payload='{"id":"evt_123","type":"invoice.paid","data":{"object":{"id":"in_123","amount_due":1000,"status":"paid"}}}'
    timestamp=$(date +%s)
    signature=$(printf "%s.%s" "$timestamp" "$payload" | openssl dgst -sha256 -hmac "$CREDIBILL_WEBHOOK_SECRET" -hex | sed 's/^.* //')

    curl -X POST https://your-app.com/api/webhooks \
      -H "Content-Type: application/json" \
      -H "X-Credibill-Timestamp: ${timestamp}" \
      -H "X-Credibill-Signature: ${signature}" \
      -d "$payload"
    ```
  </Tab>
</Tabs>

<Note>
  Verify both the signature and the timestamp (recommended 5-minute tolerance)
  and implement idempotent handlers to avoid double-processing. Always perform
  webhook verification server-side — do not verify signatures in client-side
  code.
</Note>
