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

# Security Best Practices

> Production-grade security for payment integrations

## Overview

CrediBill implements production-grade security measures. This guide explains the security features and best practices for integrating with CrediBill.

## Authentication & API Keys

### API Key Types

CrediBill uses different API keys for different purposes:

| Key Type            | Prefix   | Usage                      | Security                         |
| ------------------- | -------- | -------------------------- | -------------------------------- |
| **Secret Key**      | `cb_`    | Server-to-server API calls | Keep secret, never expose        |
| **Publishable Key** | `pk_`    | Client-side operations     | Safe to expose in frontend       |
| **Webhook Secret**  | `whsec_` | Verify webhook signatures  | Keep secret, rotate periodically |

### API Key Security

**Never commit API keys to version control:**

```bash theme={null}
# ❌ Bad
export CREDIBILL_SECRET_KEY="cb_live_abc123def456"  # Don't commit!

# ✅ Good
# .env.local (git-ignored)
CREDIBILL_SECRET_KEY=cb_live_abc123def456

# .env.example (committed)
CREDIBILL_SECRET_KEY=your_secret_key_here
```

**Rotate keys regularly:**

1. Go to **Settings → API Keys**
2. Click **Regenerate** next to key
3. Update your app with new key
4. Delete old key
5. Monitor for failed requests

**Key rotation schedule:**

* **After accidental exposure:** Immediately
* **Regular rotation:** Quarterly
* **After employee departure:** Immediately
* **After suspected compromise:** Immediately

### Using API Keys

```bash theme={null}
# ✅ Correct: API key in Authorization header
curl -X POST https://giant-goldfish-922.convex.site/api/payments \
  -H "Authorization: Bearer cb_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cust_123",
    "amount": 50000,
    "currency": "UGX"
  }'
```

**Never expose secret keys:**

```typescript theme={null}
// ❌ Wrong: Exposing secret key in frontend code
const apiKey = "cb_live_abc123"; // EXPOSED!
fetch("https://giant-goldfish-922.convex.site/api/payments", {
  headers: { Authorization: `Bearer ${apiKey}` },
});

// ❌ Wrong: Logging secret key
console.log("API Key:", process.env.CREDIBILL_SECRET_KEY); // LOGS CREDENTIALS!

// ❌ Wrong: Sending secret key to frontend
res.json({ apiKey: process.env.CREDIBILL_SECRET_KEY }); // EXPOSED!

// ✅ Correct: Keep secret key on server only
// Call CrediBill API only from your backend
app.post("/api/payments", (req, res) => {
  // Only backend knows the secret key
  // Frontend never sees it
});
```

## Webhook Security

### Signature Verification

**All webhooks must be verified.** This prevents attackers from forging webhook events.

#### Verification Implementation

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

const app = express();

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

    // Step 1: Construct signature payload
    const signaturePayload = `${timestamp}.${rawBody}`;

    // Step 2: Compute expected signature
    const webhookSecret = process.env.CREDIBILL_WEBHOOK_SECRET;
    const expectedSignature = crypto
      .createHmac("sha256", webhookSecret)
      .update(signaturePayload)
      .digest("hex");

    // Step 3: Timing-safe comparison
    let isValid = false;
    try {
      isValid = crypto.timingSafeEqual(
        Buffer.from(expectedSignature),
        Buffer.from(signature)
      );
    } catch (err) {
      isValid = false;
    }

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

    // Step 4: Verify timestamp
    const webhookTime = parseInt(timestamp);
    const now = Date.now();
    const ageMs = now - webhookTime;

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

    if (ageMs < -60 * 1000) {
      // 1 minute in future
      console.error("Webhook timestamp in future");
      return res.status(400).json({ error: "Clock skew" });
    }

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

    // Step 6: Process webhook asynchronously
    const webhook = JSON.parse(rawBody);
    handleWebhookAsync(webhook).catch((err) => {
      console.error("Webhook processing error:", err);
    });
  }
);

async function handleWebhookAsync(webhook) {
  // Process webhook here
}
```

#### Common Signature Verification Mistakes

```typescript theme={null}
// ❌ Wrong: Using parsed JSON body
app.post("/webhook", express.json(), (req, res) => {
  // req.body is already parsed, losing original raw bytes
  const rawBody = JSON.stringify(req.body); // WRONG! Different bytes
  const signature = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  // Will never match because bytes are different!
});

// ❌ Wrong: Storing raw body without encoding
const rawBody = req.body; // If Buffer, needs .toString()

// ❌ Wrong: Not doing timing-safe comparison
if (expectedSignature === signature) {
  // TIMING ATTACK VULNERABLE!
  // This comparison time varies based on mismatch position
}

// ✅ Correct: Using raw body middleware and timing-safe comparison
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body.toString("utf-8");

  // ... compute signature ...

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

### Replay Attack Prevention

CrediBill includes built-in replay attack prevention through:

1. **Timestamp validation**: Reject webhooks older than 5 minutes
2. **Event ID deduplication**: Track processed event IDs
3. **Nonce tracking**: Prevent reusing old webhooks

**Implementation:**

```typescript theme={null}
// Check timestamp (already done in signature verification)
const ageMs = Date.now() - parseInt(timestamp);
if (ageMs > 5 * 60 * 1000) {
  return res.status(400).json({ error: "Webhook too old" });
}

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

if (existingEvent) {
  console.log("Duplicate webhook, ignoring:", eventId);
  return; // Silently ignore duplicate
}

// Process webhook
await processWebhook(webhook);

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

## Credential Management

### Encrypted Storage

All payment provider credentials are encrypted using AES-256-GCM:

```typescript theme={null}
// How CrediBill stores credentials internally
function encryptCredential(plaintext, appId, masterKey) {
  // Derive app-specific key from app ID
  const appKey = crypto.createHmac("sha256", masterKey).update(appId).digest();

  // Generate random IV
  const iv = crypto.randomBytes(16);

  // Create cipher
  const cipher = crypto.createCipheriv("aes-256-gcm", appKey, iv);

  // Encrypt credential
  const encrypted =
    cipher.update(plaintext, "utf-8", "hex") + cipher.final("hex");

  // Get authentication tag
  const authTag = cipher.getAuthTag();

  // Return: iv:encrypted:authTag
  return `${iv.toString("hex")}:${encrypted}:${authTag.toString("hex")}`;
}
```

### Best Practices for Credentials

**Never log credentials:**

```typescript theme={null}
// ❌ Wrong
console.log("Provider credentials:", credentials);
console.log("Secret key:", secretKey);

// ✅ Correct
console.log("Provider:", credentials.provider);
console.log("Environment:", credentials.environment);
// Don't log sensitive values
```

**Don't expose in responses:**

```typescript theme={null}
// ❌ Wrong
res.json({
  provider: "flutterwave",
  publicKey: "FLWPUBK_TEST-xxx",
  secretKey: "FLWSECK_TEST-xxx", // EXPOSED!
});

// ✅ Correct
res.json({
  provider: "flutterwave",
  configured: true,
  environment: "test",
  // Don't include credentials in response
});
```

**Rotate credentials periodically:**

1. Go to provider dashboard
2. Generate new API credentials
3. Update in CrediBill settings
4. Rotate out old credentials
5. Delete old credentials from provider

## Transaction Security

### Race Condition Prevention

CrediBill prevents race conditions where multiple webhooks update the same transaction:

```typescript theme={null}
// Terminal state guard
async function updateTransactionStatus(transactionId, newStatus) {
  const transaction = await db.paymentTransactions.get(transactionId);

  // Never overwrite terminal states
  const terminalStates = ["success", "canceled", "refunded"];
  if (terminalStates.includes(transaction.status)) {
    throw new Error("Cannot update transaction in terminal state");
  }

  // Atomic update
  await db.paymentTransactions.patch(transactionId, {
    status: newStatus,
    completedAt: Date.now(),
  });
}
```

### Idempotency

Every payment operation should be idempotent. Include an `Idempotency-Key` header on server-side requests.

```bash theme={null}
# Example: create payment with idempotency key (cURL)
curl -X POST https://api.credibill.io/v1/payments \
  -H "Authorization: Bearer sk_live_abc123def456" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: your-unique-idempotency-key" \
  -d '{ "customerId": "cust_123", "amount": 50000, "currency": "UGX" }'
```

```ts theme={null}
// Server-side (App Router) example using fetch
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({
    customerId: "cust_123",
    amount: 50000,
    currency: "UGX",
  }),
});

// The same idempotency key ensures retries do not create duplicate payments
```

## Data Protection

### Data Retention

CrediBill retains data according to:

* **Transaction records**: 7 years (regulatory requirement)
* **Webhook logs**: 90 days
* **Customer data**: Until account deletion
* **Payment method tokens**: Until removed by customer

**Delete sensitive data:**

```bash theme={null}
# Delete customer data via API (server-side)
curl -X DELETE https://api.credibill.io/v1/customers/cust_123 \
  -H "Authorization: Bearer sk_live_abc123def456"

# All associated payment data is also deleted (where applicable)
```

### PCI Compliance

CrediBill is PCI DSS compliant:

* ✅ Never stores raw card numbers
* ✅ Encrypted transmission (TLS 1.2+)
* ✅ Secure credential storage (AES-256-GCM)
* ✅ Access logging and monitoring
* ✅ Regular penetration testing

**Your responsibility:**

* ✅ Never accept raw card data on your server
* ✅ Use CrediBill's tokenization for cards
* ✅ Don't log or store card numbers
* ✅ Use HTTPS for all communication
* ✅ Validate SSL certificates

## Network Security

### TLS/HTTPS

All CrediBill endpoints require HTTPS. Verify TLS by making requests over `https://` and ensuring your server enforces HTTPS for webhook endpoints.

```bash theme={null}
# Check connectivity and TLS by making an HTTPS request
curl -I https://api.credibill.io/v1/ \
  -H "Authorization: Bearer $CREDIBILL_SECRET_KEY"
```

**Webhook endpoints must be HTTPS:**

```bash theme={null}
# ❌ Wrong: HTTP webhook will be rejected
# Input: http://your-app.com/webhooks/credibill

# ✅ Correct: HTTPS webhook
# Input: https://your-app.com/webhooks/credibill
# Ensure your server accepts only HTTPS
```

### TLS Certificate Validation

```typescript theme={null}
// ✅ Correct: TLS validation enabled by default
import https from "https";

const options = {
  hostname: "api.credibill.io",
  port: 443,
  rejectUnauthorized: true, // Validate certificate
};

https.get(options, (res) => {
  // Certificate is validated automatically
});

// ❌ Wrong: Disabling certificate validation
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; // INSECURE!
```

## Monitoring & Alerting

### Security Events to Monitor

```typescript theme={null}
// Log security-relevant events
async function logSecurityEvent(type, details) {
  await db.securityLogs.create({
    type,
    details,
    timestamp: Date.now(),
    userId: currentUser?.id,
    ipAddress: req.ip,
    userAgent: req.headers["user-agent"],
  });

  // Alert on critical events
  if (["unauthorized_access", "credential_compromise"].includes(type)) {
    await alertSecurityTeam(type, details);
  }
}

// Monitor these events:
// - Invalid webhook signatures
// - Multiple failed API authentications
// - Credential access from unusual locations
// - Large payment amounts
// - Unusual payment patterns
// - Failed payment retries
```

### Audit Logging

```typescript theme={null}
// Log all sensitive operations
async function logAuditEvent(action, subject, changes) {
  await db.auditLogs.create({
    action,
    subject,
    changes,
    timestamp: Date.now(),
    userId: currentUser?.id,
    ipAddress: req.ip,
    userAgent: req.headers["user-agent"],
  });
}

// Audit log examples:
await logAuditEvent("credential_created", "payment_provider", {
  provider: "flutterwave",
  environment: "live",
});

await logAuditEvent("credential_rotated", "api_key", {
  oldKeyId: "...",
  newKeyId: "...",
});

await logAuditEvent("payment_initiated", "transaction", {
  customerId: "cust_123",
  amount: 50000,
  currency: "UGX",
});
```

## Access Control

### Role-Based Access

Implement role-based access control:

```typescript theme={null}
// Admin: Full access
if (user.role === "admin") {
  // Can view all payments, credentials, users
}

// Finance: View only, no modifications
if (user.role === "finance") {
  // Can view payments, invoices
  // Cannot modify credentials, delete data
}

// Developer: API access
if (user.role === "developer") {
  // Can create/view API keys
  // Can configure webhooks
  // Cannot access payment data
}

// Support: Limited customer support
if (user.role === "support") {
  // Can view customer subscriptions
  // Cannot view payment methods
  // Cannot modify configurations
}
```

### API Key Scoping

```typescript theme={null}
// Some API keys should have limited scope:

// Full access key (keep secure)
const fullAccessKey = "sk_live_abc123def456";

// Read-only key (can expose in logs)
const readOnlyKey = "sk_live_ro_xyz789abc123";

// Webhook-specific key (limited to webhooks)
const webhookKey = "sk_live_wh_def456ghi789";

// Each key has:
// - Specific permissions (read, write, delete)
// - Resource restrictions (payments only, webhooks only)
// - IP whitelisting (optional)
// - Rate limits
```

## Incident Response

### If Credentials Are Compromised

1. **Immediately revoke** the exposed key/credential
2. **Generate new** key/credential
3. **Update** all applications with new credentials
4. **Review logs** for unauthorized usage
5. **Monitor** for suspicious activity
6. **Contact provider** to revoke provider credentials if exposed
7. **Document** the incident

### If Payment Data Is Exposed

1. **Assess scope**: Which data was exposed?
2. **Notify users**: If PII/payment method exposed
3. **Contact CrediBill**: Report the incident
4. **Enable monitoring**: Watch for fraudulent activity
5. **Review logs**: Understand how exposure occurred
6. **Implement fixes**: Prevent recurrence

### If Webhooks Are Being Spoofed

1. **Verify signature**: Ensure proper verification
2. **Check logs**: Look for pattern of invalid signatures
3. **Check IP source**: See where invalid webhooks come from
4. **Block IP**: Add to firewall if needed
5. **Rotate webhook secret**: Generate new secret
6. **Update endpoint**: If verification code has bug

## Compliance

### Supported Standards

CrediBill complies with:

* **PCI DSS 3.2.1**: Payment Card Industry Data Security Standard
* **GDPR**: General Data Protection Regulation (EU)
* **CCPA**: California Consumer Privacy Act
* **HIPAA**: Health Insurance Portability (if applicable)
* **SOC 2 Type II**: Service Organization Control

### Your Compliance Responsibilities

**Privacy:**

* ✅ Collect customer data with consent
* ✅ Provide privacy policy
* ✅ Allow data deletion (GDPR right to be forgotten)
* ✅ Handle data breaches
* ✅ Keep PII secure

**PCI:**

* ✅ Never store raw card numbers
* ✅ Use tokenization for cards
* ✅ Maintain HTTPS
* ✅ Validate SSL certificates
* ✅ Log and monitor access

**Financial:**

* ✅ Maintain transaction records (7 years)
* ✅ Keep audit logs
* ✅ Reconcile with provider regularly
* ✅ Report suspiciously large transactions
* ✅ Comply with local regulations

## Security Checklist

Before going to production:

### Setup

* [ ] All credentials in environment variables (not hardcoded)
* [ ] API keys rotated (first time setup)
* [ ] Webhook secret saved securely
* [ ] HTTPS enabled on webhook endpoint
* [ ] SSL certificate valid (not self-signed)

### Code

* [ ] Webhook signature verification implemented
* [ ] Timing-safe comparison used
* [ ] Timestamp validation implemented
* [ ] Replay attack prevention in place
* [ ] Idempotency keys used for payments
* [ ] Error messages don't expose credentials
* [ ] Logging doesn't log sensitive data

### Monitoring

* [ ] Invalid webhook attempts logged
* [ ] Failed API auth attempts monitored
* [ ] Security alerts configured
* [ ] Audit logs enabled
* [ ] Payment anomalies monitored

### Operational

* [ ] Key rotation plan documented
* [ ] Incident response plan documented
* [ ] Team training completed
* [ ] Backup credentials stored securely
* [ ] Regular security reviews scheduled

## Resources

* [OWASP Security Best Practices](https://owasp.org/)
* [PCI DSS Compliance](https://www.pcisecuritystandards.org/)
* [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
* [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
