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

# Troubleshooting & FAQ

> Common issues and solutions

## Troubleshooting

### Payment Issues

#### Payment Stuck in "Pending"

**Symptoms:** Transaction created but status never changes to success/failed

**Common causes:**

1. **Mobile money delays**: MMOs can take 1-5 minutes
2. **Webhook delivery failure**: Provider sent webhook but we didn't receive it
3. **Provider API error**: Provider accepted payment but network error on response

**Solution:**

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

    export async function GET(
      request: Request,
      { params }: { params: { id: string } }
    ) {
      const id = params.id;
      const res = await fetch(
        `https://giant-goldfish-922.convex.site/api/payments/${id}`,
        {
          headers: { Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}` },
        }
      );
      return NextResponse.json(await res.json(), { status: res.status });
    }

    // app/api/webhook-logs/route.ts (example)
    export async function POST(request: Request) {
      const body = await request.json();
      const res = await fetch(
        "https://giant-goldfish-922.convex.site/api/webhook-logs",
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify(body),
        }
      );
      return NextResponse.json(await res.json(), { status: res.status });
    }
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # Check transaction status
    curl -X GET https://giant-goldfish-922.convex.site/api/payments/txn_abc123 \
      -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}"

    # List webhook logs filtered by payment transaction
    curl -X GET "https://giant-goldfish-922.convex.site/api/webhook-logs?paymentTransactionId=txn_abc123" \
      -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}"
    ```
  </Tab>
</Tabs>

If webhook never delivered: 1) Check provider dashboard for successful payment 2) Contact provider support 3) Manually update transaction status as workaround.

**Next steps:**

* Wait 5-10 minutes for MMO confirmation
* Check provider dashboard for payment status
* Check CrediBill webhook logs for delivery attempts
* If payment confirmed in provider but not in CrediBill, contact support

#### Payment Declined

**Symptoms:** Payment fails immediately with "declined" status

**Common causes:**

1. **Insufficient funds**: Customer doesn't have balance
2. **Invalid payment method**: Phone number or card incorrect
3. **Account blocked**: Account frozen by provider
4. **Daily limit reached**: Customer exceeded daily transaction limit

**Solution:**

<Tabs>
  <Tab title="Next.js (App Router)">
    ```ts theme={null}
    // app/api/payments/[id]/route.ts
    // (GET handler shown earlier)
    // Use that endpoint to inspect failure fields from the payment object
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # Inspect failure details
    curl -X GET https://giant-goldfish-922.convex.site/api/payments/txn_abc123 \
      -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" | jq .failureReason,.failureCode,.providerResponse
    ```
  </Tab>
</Tabs>

Common failure codes include: INSUFFICIENT\_FUNDS, INVALID\_ACCOUNT, DECLINED, DAILY\_LIMIT, ACCOUNT\_BLOCKED.

**Customer actions:**

* Ensure sufficient funds in mobile money wallet
* Verify correct phone number
* Check account not blocked by provider
* Retry with different payment method

#### Multiple Charges for Single Payment

**Symptoms:** Customer charged multiple times for one subscription

**Causes:**

1. **Missing idempotency**: Payment initiated multiple times
2. **Client-side double submission**: Form submitted twice
3. **Webhook retry processing**: Same webhook processed multiple times

**Solution:**

```js theme={null}
// 1. Use an Idempotency-Key header for server-side payment creation
const idempotencyKey = crypto.randomUUID();

// Server-side (Next.js App Router)
// app/api/payments/route.ts (POST)
const res = await fetch("https://api.credibill.io/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
  },
  body: JSON.stringify({ subscriptionId: "sub_abc123", amount: 50000 }),
});

// 2. Prevent double-submission in UI
let isSubmitting = false;
async function submitPayment() {
  if (isSubmitting) return;
  isSubmitting = true;
  try {
    await fetch("/api/payments", {
      method: "POST",
      body: JSON.stringify({
        /* ... */
      }),
    });
  } finally {
    isSubmitting = false;
  }
}

// 3. Webhook idempotency (database sketch)
const eventId = `${webhook.event}:${webhook.data.id}:${webhook.timestamp}`;
const existing = await db.processedEvents.findOne({ where: { eventId } });
if (existing) return; // already processed
await db.processedEvents.create({ data: { eventId, processedAt: new Date() } });

// Process webhook safely here
```

### Webhook Issues

#### Webhooks Not Received

**Symptoms:** Webhook endpoint configured but never receives calls

**Checklist:**

* [ ] Webhook URL is public (not localhost)
* [ ] HTTPS certificate is valid
* [ ] Firewall allows CrediBill IPs
* [ ] Endpoint returns 200 OK
* [ ] Endpoint responds within 10 seconds
* [ ] Correct provider webhook secret configured

**Solution:**

```typescript theme={null}
// 1. Test endpoint manually
curl -X POST https://your-app.com/webhooks/credibill \
  -H "Content-Type: application/json" \
  -d '{"event":"test","data":{}}'

// Should return 200

// 2. Check webhook logs in CrediBill dashboard
// Settings → Webhooks → Logs

// 3. Enable verbose logging
app.post('/webhooks/credibill', (req, res) => {
  console.log('Webhook received:');
  console.log('  Headers:', req.headers);
  console.log('  Body size:', req.body?.length);
  console.log('  Signature:', req.headers['x-credibill-signature']?.substring(0, 10) + '...');

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

// 4. Check firewall/WAF logs
// Look for CrediBill IPs being blocked

// 5. If still not receiving, contact support with:
//   - Webhook URL
//   - Logs from your endpoint
//   - Logs from CrediBill dashboard
```

#### Webhook Signature Verification Fails

**Symptoms:** All webhooks rejected with "Invalid signature" error

**Checklist:**

* [ ] Using raw body (not parsed JSON)
* [ ] Webhook secret is correct
* [ ] Using timing-safe comparison
* [ ] Timestamp header present
* [ ] Not modifying body before verification

**Solution:**

```typescript theme={null}
// ❌ Common mistake: Using parsed body
app.use(express.json()); // Parses body, loses original bytes
app.post("/webhook", (req, res) => {
  const body = JSON.stringify(req.body); // Different bytes!
  // Signature verification will fail
});

// ✅ Correct: Using raw body
app.post(
  "/webhook",
  express.raw({ type: "application/json" }), // Get raw bytes
  (req, res) => {
    const rawBody = req.body.toString("utf-8"); // Original bytes
    const timestamp = req.headers["x-credibill-timestamp"];

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

    // Timing-safe comparison
    let isValid = false;
    try {
      isValid = crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(req.headers["x-credibill-signature"])
      );
    } catch (err) {
      isValid = false;
    }

    if (!isValid) {
      console.log("Expected:", expected);
      console.log("Received:", req.headers["x-credibill-signature"]);
      console.log("Payload length:", payload.length);
      console.log(
        "Webhook secret:",
        process.env.CREDIBILL_WEBHOOK_SECRET?.substring(0, 10) + "..."
      );
    }
  }
);
```

**Debug signature mismatch:**

```typescript theme={null}
// Log signature details
console.log("Timestamp:", timestamp);
console.log("Body length:", rawBody.length);
console.log("Body (first 100 chars):", rawBody.substring(0, 100));
console.log("Webhook secret:", secret.substring(0, 10) + "...");
console.log("Expected signature:", expectedSignature);
console.log("Received signature:", receivedSignature);

// Regenerate signature step-by-step
const payload = `${timestamp}.${rawBody}`;
console.log("Payload to hash:", payload.substring(0, 100) + "...");

const hmac = crypto.createHmac("sha256", secret);
hmac.update(payload);
const computed = hmac.digest("hex");

console.log("Computed signature:", computed);
console.log("Do they match?", computed === receivedSignature);
```

#### Duplicate Webhooks

**Symptoms:** Same event processed multiple times

**Cause:** Network timeout causing CrediBill to retry webhook

**Solution:**

```typescript theme={null}
// Implement idempotency checking
async function handleWebhook(webhook) {
  // Create unique event ID
  const eventId = `${webhook.event}:${webhook.data.id}:${webhook.timestamp}`;

  // Check if already processed
  const existing = await db.processedWebhooks.findOne({ eventId });
  if (existing) {
    console.log("Duplicate webhook, ignoring:", eventId);
    return; // Silently ignore
  }

  // Process webhook
  try {
    await processEvent(webhook);

    // Mark as processed AFTER successful processing
    await db.processedWebhooks.create({
      eventId,
      processedAt: new Date(),
    });
  } catch (error) {
    console.error("Error processing webhook:", error);
    // Don't mark as processed if it failed
    throw error; // Let CrediBill retry
  }
}
```

### Credential & Configuration Issues

#### "Invalid Credentials" Error

**Symptoms:** Provider connection test fails immediately

**Checklist:**

* [ ] Credentials copied correctly (no extra spaces)
* [ ] Using correct credentials (test vs. live)
* [ ] Credentials still valid in provider dashboard
* [ ] Not using URL-encoded credentials

**Solution:**

```bash theme={null}
# 1. Verify credentials in provider dashboard
# Each provider has different credential format

# Flutterwave:
# Public Key: FLWPUBK_TEST-xxx or FLWPUBK-xxx
# Secret Key: FLWSECK_TEST-xxx or FLWSECK-xxx

# PawaPay:
# API Token: Bearer test_xxx or Bearer prod_xxx

# Pesapal:
# Consumer Key: xxx
# Consumer Secret: xxx

# DPO:
# Company Token: xxx
# Service Type: xxx

# 2. Check for extra spaces
echo "Credential: |${CREDENTIAL}|" # Pipes show any spaces

# 3. Try credential in provider's test endpoint
curl -H "Authorization: Bearer ${API_TOKEN}" https://api.provider.com/test

# 4. In CrediBill, click "Test Connection" after updating
```

#### Provider Connection Keeps Failing

**Symptoms:** Test connection fails repeatedly

**Checklist:**

* [ ] Credentials are current (not expired)
* [ ] Provider account active (not suspended)
* [ ] Network connectivity working
* [ ] Firewall allows outbound to provider
* [ ] Provider API not down

**Solution:**

```bash theme={null}
# 1. Check provider status via CrediBill API
curl -X GET https://api.credibill.io/v1/payment-providers/provider_id \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" | jq '{connectionStatus: .connectionStatus, errorMessage: .errorMessage, lastTestedAt: .lastTestedAt}'

# 2. Test provider health directly (isolate)
curl -I https://api.provider.com/health
```

```ts theme={null}
// Server-side check using fetch
const res = await fetch(
  `https://api.credibill.io/v1/payment-providers/provider_id`,
  {
    headers: { Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}` },
  }
);
const provider = await res.json();
console.log("Status:", provider.connectionStatus);
```

// 3. Check provider status page
// Flutterwave: [https://status.flutterwave.com/](https://status.flutterwave.com/)
// PawaPay: Check their status
// Pesapal: [https://status.pesapal.com/](https://status.pesapal.com/)
// DPO: Check their status

// 4. Contact provider support with:
// - Error message
// - Credentials (masked)
// - Time of failure
// - Your account ID

````

### API & Integration Issues

#### "Unauthorized" Error on API Calls

**Symptoms:** API requests return 401 Unauthorized

**Causes:**

1. **Missing or invalid API key**
2. **Incorrect API key** (using publishable instead of secret)
3. **API key rotated** but app not updated
4. **Invalid Bearer token format**

**Solution:**

```bash
# ✅ Correct: Use your Secret Key in server-side requests
curl -X GET https://api.credibill.io/v1/customers \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" \
  -H "Content-Type: application/json"

# If you get 401 Unauthorized, check:
# 1. You're using a secret key (prefix sk_)
# 2. The key has not been rotated or revoked
# 3. The key is correctly copied into your environment
````

**Server-side check (Next.js App Router example)**

```ts theme={null}
// app/api/health/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  const res = await fetch("https://api.credibill.io/v1/customers", {
    headers: { Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}` },
  });

  if (res.status === 401) {
    return NextResponse.json(
      { ok: false, error: "Unauthorized" },
      { status: 401 }
    );
  }

  return NextResponse.json({ ok: true });
}
```

# Troubleshooting tips

* Ensure the secret key is set in server environment variables, not in frontend code.
* Use cURL or a server-side route to test keys.
* Verify the key prefix is `sk_` (secret keys) not `pk_` (publishable keys).

  console.error("3. Key matches environment");

  }
  }

````

### Database & Data Issues

#### Missing or Mismatched Data

**Symptoms:** Subscription created but not appearing in API

**Causes:**

1. **Using wrong environment** (test vs. live)
2. **Multi-tenant isolation**: Data in different app
3. **Database sync delay**: Eventual consistency
4. **Wrong pagination**: Data on different page

**Solution:**

```bash
# 1. Verify environment (check subscription)
curl -X GET https://api.credibill.io/v1/subscriptions/sub_abc123 \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" | jq .createdAt

# 2. Check multi-tenant isolation (list apps)
curl -X GET https://api.credibill.io/v1/apps \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" | jq '.data[] | {id, name}'

# 3. List subscriptions and find customer
curl -X GET "https://api.credibill.io/v1/subscriptions?limit=100" \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}" | jq '.data[] | select(.customerId=="cust_xyz")'

# 4. Handle eventual consistency: retry after a short delay
sleep 5
curl -X GET https://api.credibill.io/v1/subscriptions/sub_abc123 \
  -H "Authorization: Bearer ${CREDIBILL_SECRET_KEY}"
````

```ts theme={null}
// Example server-side check (App Router) using fetch
async function checkSubscription(id) {
  const res = await fetch(`https://api.credibill.io/v1/subscriptions/${id}`, {
    headers: { Authorization: `Bearer ${process.env.CREDIBILL_SECRET_KEY}` },
  });
  return await res.json();
}
```

## Frequently Asked Questions

### Billing & Pricing

**Q: How much does CrediBill cost?**

A: See [pricing page](https://credibill.tech/pricing). Typically a percentage of transaction amount plus per-transaction fee.

**Q: Do you charge for failed payments?**

A: No. We only charge for successful transactions.

**Q: Can you help with invoicing?**

A: Yes, CrediBill generates and sends invoices automatically. You can customize templates in dashboard.

### Technical

**Q: What happens if a provider API is down?**

A: CrediBill failsover to backup provider if configured. Payment marked as pending and retried later.

**Q: How long do payment retries continue?**

A: Up to 3 automatic retries with exponential backoff. After 3 failures, subscription marked PAST\_DUE.

**Q: Can I use multiple providers simultaneously?**

A: Yes. Configure primary and backup. Can also route by customer country/payment method.

**Q: What happens if my webhook endpoint is down?**

A: CrediBill retries webhooks up to 3 times with backoff. After 3 failures, marked as failed. Check webhook logs to manually retry.

**Q: How long does a mobile money payment take?**

A: Instant to 5 minutes typical. Some edge cases up to 30 minutes. Check provider dashboard.

### Security

**Q: Are my credentials encrypted?**

A: Yes. AES-256-GCM encryption with per-app keys. In transit uses HTTPS/TLS 1.2+.

**Q: Do you store card numbers?**

A: No. We never handle raw card data. Providers handle PCI compliance.

**Q: How do you prevent fraud?**

A: Signature verification, replay attack prevention, rate limiting, anomaly detection. For high-risk transactions, may request additional verification.

**Q: Can I get an audit trail?**

A: Yes. View all transactions, webhooks, and configuration changes in dashboard. Export available for compliance.

### Integration

**Q: How long does integration take?**

A: 2-4 hours with CrediBill docs. Additional time for provider setup.

**Q: Do you have SDKs?**

A: Yes, for Node.js, Python, Go, Ruby. REST API for any language.

**Q: Can I test before going live?**

A: Yes. All providers have test mode. Use test credentials and payment numbers.

**Q: How do I handle different currencies?**

A: Each provider supports specific currencies. Configure per provider. Automatic currency conversion available.

**Q: Can I customize invoice templates?**

A: Yes. Dashboard has template editor. Can add logo, colors, custom fields.

### Support & Troubleshooting

**Q: How do I contact support?**

A: Email [support@credibill.tech](mailto:support@credibill.tech) or use in-app chat. Response time \< 2 hours for critical issues.

**Q: What's your SLA?**

A: 99.95% uptime SLA. Check our status page for incidents.

**Q: Can you help debug payment issues?**

A: Yes. We can check logs on our side. Provide transaction ID and time.

**Q: What if I lose webhook logs?**

A: Logs retained for 90 days. Older logs available by contacting support.

**Q: How do I cancel my account?**

A: Go to Settings → Account → Delete Account. All data deleted within 30 days per GDPR.

## Still Need Help?

* **Email**: [support@credibill.tech](mailto:support@credibill.tech)
* **Chat**: Available in dashboard
* **Status Page**: [https://status.credibill.tech/](https://status.credibill.tech/)
* **Community**: Join our Discord
