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

# Webhooks

> Real-time event notifications from FossaPay

## What are Webhooks?

Webhooks are HTTP callbacks that FossaPay sends to your server when events occur in your account. They enable you to receive real-time notifications about payments, payouts, settlements, and other important events.

<Info>
  Webhooks eliminate the need for polling and ensure your application stays in sync with FossaPay in real-time.
</Info>

## Why Use Webhooks?

<CardGroup cols={2}>
  <Card color="#0080FF" title="Real-time Updates" icon="bolt">
    Get notified immediately when events occur
  </Card>

  <Card color="#0080FF" title="Reduce Polling" icon="clock">
    No need to constantly check for updates
  </Card>

  <Card color="#0080FF" title="Automated Workflows" icon="robot">
    Trigger actions automatically based on events
  </Card>

  <Card color="#0080FF" title="Better UX" icon="smile">
    Update your users instantly about transaction status
  </Card>
</CardGroup>

## Webhook Events

### Deposit Events

| Event               | Description                    | When it fires                            |
| ------------------- | ------------------------------ | ---------------------------------------- |
| `deposit.completed` | Deposit completed successfully | When a deposit is successfully processed |

### Withdrawal Events

| Event                  | Description                       | When it fires                               |
| ---------------------- | --------------------------------- | ------------------------------------------- |
| `withdrawal.completed` | Withdrawal completed successfully | When a withdrawal is successfully processed |

### Transfer Events

| Event                | Description                     | When it fires                             |
| -------------------- | ------------------------------- | ----------------------------------------- |
| `transfer.completed` | Transfer completed successfully | When a transfer is successfully processed |
| `transfer.failed`    | Transfer failed                 | When a transfer fails to process          |

### Wallet Events

| Event                    | Description            | When it fires                    |
| ------------------------ | ---------------------- | -------------------------------- |
| `wallet.balance.updated` | Wallet balance updated | When a wallet balance is updated |

## Setting Up Webhooks

### Configure Webhook URL

Set your webhook endpoint in the dashboard or via API:

**Via Dashboard:**

1. Go to [Settings → Webhooks](https://dashboard.fossapay.com/settings?tab=webhooks)
2. Enter your webhook URL
3. Copy the webhook secret

**Via API:**

```javascript theme={null}
await fossapay.webhooks.configure({
  url: 'https://your-app.com/webhooks/fossapay',
  events: [
    'deposit.completed',
    'withdrawal.completed',
    'transfer.completed',
    'transfer.failed',
    'wallet.balance.updated'
  ],
  secret: 'your-webhook-secret' // Optional: for signature verification
});
```

### Webhook URL Requirements

Your webhook endpoint must:

* Use HTTPS (HTTP not allowed in production)
* Respond within 30 seconds
* Return a 2xx status code for successful receipt
* Handle duplicate events gracefully

<Warning>
  Webhook URLs must be publicly accessible. Use services like [ngrok](https://ngrok.com) for local development.
</Warning>

## Webhook Payload

All webhooks follow this structure:

```json theme={null}
{
  "event": "deposit.completed",
  "eventId": "evt_abc123xyz",
  "timestamp": "2024-01-15T10:30:45Z",
  "data": {
    "transactionId": "txn_001",
    "customerId": "cus_123",
    "sender": {
      "address": "0x123...",
      "chain": "Ethereum"
    },
    "recipient": {
      "address": "0x456...",
      "chain": "Ethereum"
    },
    "amount": 1000000,
    "currency": "USDT",
    "reference": "dep-20240115-001",
    "status": "completed",
    "transactionType": "deposit",
    "blockchain": "Ethereum",
    "transactionHash": "0x789...",
    "explorerLink": "https://etherscan.io/tx/0x789...",
    "timestamp": "2024-01-15T10:30:40Z"
  }
}
```

## Implementing Webhook Handler

### Basic Handler (Node.js/Express)

```javascript theme={null}
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/webhooks/fossapay', async (req, res) => {
  // 1. Verify webhook signature
  const signature = req.headers['x-fossapay-signature'];
  const secret = process.env.FOSSAPAY_WEBHOOK_SECRET;
  if (!verifySignature(req.body, signature, secret)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Respond quickly (within 30 seconds)
  res.status(200).send('Webhook received');

  // 3. Process webhook asynchronously
  processWebhook(req.body).catch(err => {
    console.error('Webhook processing failed:', err);
  });
});

async function processWebhook(payload) {
  const { event, data } = payload;

  switch (event) {
    case 'deposit.completed':
      await handleDepositCompleted(data);
      break;

    case 'withdrawal.completed':
      await handleWithdrawalCompleted(data);
      break;

    case 'transfer.completed':
      await handleTransferCompleted(data);
      break;

    case 'transfer.failed':
      await handleTransferFailed(data);
      break;

    case 'wallet.balance.updated':
      await handleWalletBalanceUpdated(data);
      break;

    default:
      console.log('Unhandled event:', event);
  }
}

async function handleDepositCompleted(data) {
  // Credit customer wallet
  await db.transactions.create({
    id: data.transactionId,
    customer_id: data.customerId,
    amount: data.amount,
    type: 'credit',
    status: 'completed'
  });

  await db.wallets.increment({
    customer_id: data.customerId,
    amount: data.amount
  });

  // Notify customer
  await sendNotification(data.customerId, {
    title: 'Deposit Received',
    message: `Your wallet has been credited with ${data.amount} ${data.currency}`
  });
}

function verifySignature(payload, signature, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(hash, 'hex')
  );
}

app.listen(3000);
```

### Python/Flask Example

```python theme={null}
from flask import Flask, request, jsonify
import hashlib
import hmac
import json

app = Flask(__name__)

@app.route('/webhooks/fossapay', methods=['POST'])
def handle_webhook():
    # Verify signature
    signature = request.headers.get('x-fossapay-signature')
    secret = os.environ['FOSSAPAY_WEBHOOK_SECRET']
    if not verify_signature(request.data, signature, secret):
        return 'Invalid signature', 401

    # Respond quickly
    payload = request.json

    # Process asynchronously
    process_webhook.delay(payload)  # Using Celery

    return 'Webhook received', 200

def verify_signature(payload, signature, secret):
    hash = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(hash, signature)

def process_webhook(payload):
    event = payload['event']
    data = payload['data']

    if event == 'deposit.completed':
        handle_deposit_completed(data)
    elif event == 'withdrawal.completed':
        handle_withdrawal_completed(data)
    elif event == 'transfer.completed':
        handle_transfer_completed(data)
    elif event == 'transfer.failed':
        handle_transfer_failed(data)
    elif event == 'wallet.balance.updated':
        handle_wallet_balance_updated(data)
```

## Security

### Signature Verification

FossaPay signs all webhooks with HMAC SHA256:

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(signature)
  );
}
```

<Tip>
  Use IP whitelisting as an additional layer of security, not as a replacement for signature verification.
</Tip>

## Retry Logic

If your endpoint doesn't respond with a 2xx status, FossaPay retries:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 5 minutes  |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours    |
| 4th retry | 6 hours    |
| 5th retry | 24 hours   |

After 5 failed attempts, the webhook is marked as failed and no more retries occur.

<Warning>
  Check your webhook logs in the dashboard for failed deliveries and investigate issues promptly.
</Warning>

## Idempotency

Webhooks may be delivered more than once. Implement idempotent handling:

```javascript theme={null}
async function handleDepositCompleted(data) {
  const existingTransaction = await db.transactions.findOne({
    id: data.transactionId
  });

  if (existingTransaction) {
    console.log('Transaction already processed');
    return; // Skip duplicate
  }

  // Process deposit
  await db.transactions.create({
    id: data.transactionId,
    // ... other fields
  });
}
```

## Testing Webhooks

### Local Testing with ngrok

```bash theme={null}
# Start ngrok
ngrok http 3000

# Use the HTTPS URL in FossaPay dashboard
# Example: https://abc123.ngrok.io/webhooks/fossapay
```

### Simulate Webhooks

Test your webhook handler with simulated events:

```bash theme={null}
curl -X POST https://api-production.fossapay.com/api/v1/webhooks/simulate \
  -H "Authorization: Bearer fp_test_sk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "deposit.completed",
    "customer_id": "cus_123",
    "amount": 1000000,
    "currency": "USDT"
  }'
```

### Webhook Logs

View webhook delivery logs in your dashboard:

1. Go to [Settings → Webhooks](https://dashboard.fossapay.com/settings?tab=webhooks)
2. Scroll down to "Webhook Events"
3. Filter by event type
4. Replay failed webhooks

## Best Practices

<AccordionGroup>
  <Accordion title="Respond Quickly">
    Always return 200 immediately, then process asynchronously:

    ```javascript theme={null}
    app.post('/webhooks', (req, res) => {
      res.status(200).send('OK');
      processAsync(req.body); // Don't await
    });
    ```
  </Accordion>

  <Accordion title="Verify Signatures">
    Never trust webhooks without signature verification:

    ```javascript theme={null}
    if (!verifySignature(payload, signature, secret)) {
      return res.status(401).send('Unauthorized');
    }
    ```
  </Accordion>

  <Accordion title="Handle Duplicates">
    Use event\_id or transaction\_id to detect duplicates:

    ```javascript theme={null}
    const processed = await redis.get(`webhook:${event_id}`);
    if (processed) return;

    await redis.set(`webhook:${event_id}`, '1', 'EX', 86400);
    ```
  </Accordion>

  <Accordion title="Log Everything">
    Log all webhook receipts for debugging and auditing:

    ```javascript theme={null}
    logger.info('Webhook received', {
      event_id: payload.eventId,
      event: payload.event,
      timestamp: payload.timestamp
    });
    ```
  </Accordion>

  <Accordion title="Monitor Failures">
    Set up alerts for webhook failures:

    ```javascript theme={null}
    if (failureCount > threshold) {
      await sendAlert('Webhook failures detected');
    }
    ```
  </Accordion>
</AccordionGroup>

## Webhook Payload Examples

### Deposit Completed (Crypto)

```json theme={null}
{
  "event": "deposit.completed",
  "eventId": "evt_abc123",
  "timestamp": "2024-01-15T10:30:45Z",
  "data": {
    "transactionId": "txn_001",
    "customerId": "cus_123",
    "sender": {
      "address": "0x123...",
      "chain": "Ethereum"
    },
    "recipient": {
      "address": "0x456...",
      "chain": "Ethereum"
    },
    "amount": 1000000,
    "currency": "USDT",
    "reference": "dep-20240115-001",
    "status": "completed",
    "transactionType": "deposit",
    "blockchain": "Ethereum",
    "transactionHash": "0x789...",
    "explorerLink": "https://etherscan.io/tx/0x789...",
    "timestamp": "2024-01-15T10:30:40Z"
  }
}
```

### Deposit Completed (Fiat)

```json theme={null}
{
  "event": "deposit.completed",
  "eventId": "evt_def456",
  "timestamp": "2024-01-15T11:00:00Z",
  "data": {
    "transactionId": "txn_002",
    "customerId": "cus_456",
    "sender": {
      "accountName": "John Doe",
      "accountNumber": "0987654321",
      "bankName": "GTBank"
    },
    "recipient": {
      "accountName": "Jane Smith",
      "accountNumber": "0123456789",
      "bankName": "GTBank"
    },
    "amount": 50000,
    "currency": "NGN",
    "fee": 500,
    "reference": "dep-20240115-002",
    "status": "completed",
    "transactionType": "deposit",
    "notes": "Bank transfer deposit",
    "timestamp": "2024-01-15T11:00:00Z"
  }
}
```

### Withdrawal Completed

```json theme={null}
{
  "event": "withdrawal.completed",
  "eventId": "evt_xyz789",
  "timestamp": "2024-01-15T11:45:23Z",
  "data": {
    "transactionId": "txn_003",
    "customerId": "cus_789",
    "recipient": {
      "accountName": "Jane Smith",
      "accountNumber": "0123456789",
      "bankName": "GTBank"
    },
    "amount": 25000,
    "currency": "NGN",
    "reference": "wth-20240115-001",
    "status": "completed",
    "transactionType": "withdrawal",
    "timestamp": "2024-01-15T11:45:20Z"
  }
}
```

### Transfer Completed

```json theme={null}
{
  "event": "transfer.completed",
  "eventId": "evt_transfer1",
  "timestamp": "2024-01-15T12:30:00Z",
  "data": {
    "transactionId": "txn_004",
    "customerId": "cus_123",
    "sender": {
      "address": "0x123...",
      "chain": "Ethereum"
    },
    "recipient": {
      "address": "0x456...",
      "chain": "Ethereum"
    },
    "amount": 500000,
    "currency": "USDT",
    "reference": "trf-20240115-001",
    "status": "completed",
    "transactionType": "transfer",
    "blockchain": "Ethereum",
    "transactionHash": "0xabc...",
    "timestamp": "2024-01-15T12:30:00Z"
  }
}
```

### Transfer Failed

```json theme={null}
{
  "event": "transfer.failed",
  "eventId": "evt_transfer2",
  "timestamp": "2024-01-15T13:15:00Z",
  "data": {
    "transactionId": "txn_005",
    "customerId": "cus_456",
    "sender": {
      "address": "0x789...",
      "chain": "Ethereum"
    },
    "recipient": {
      "address": "0xabc...",
      "chain": "Ethereum"
    },
    "amount": 300000,
    "currency": "USDT",
    "reference": "trf-20240115-002",
    "status": "failed",
    "transactionType": "transfer",
    "blockchain": "Ethereum",
    "transactionHash": "0xdef...",
    "timestamp": "2024-01-15T13:15:00Z"
  }
}
```

### Wallet Balance Updated

```json theme={null}
{
  "event": "wallet.balance.updated",
  "eventId": "evt_balance1",
  "timestamp": "2024-01-15T14:00:00Z",
  "data": {
    "fromAccount": {
      "accountNumber": "0123456789",
      "accountName": "Main Wallet",
      "bankName": "GTBank"
    },
    "toAccount": {
      "accountNumber": "0987654321",
      "accountName": "Savings Wallet",
      "bankName": "GTBank"
    },
    "amount": 25000,
    "currency": "NGN",
    "reference": "bal-20240115-001",
    "type": "withdrawal/deposit",
    "customerId": "cus_123",
    "narration": "Transfer between accounts",
    "timestamp": "2024-01-15T14:00:00Z"
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card color="#0080FF" title="Configure Webhooks" icon="gear" href="https://dashboard.fossapay.com/settings?tab=webhooks">
    Set up your webhook endpoint
  </Card>

  <Card color="#0080FF" title="View Webhook Logs" icon="list" href="https://dashboard.fossapay.com/settings?tab=webhooks">
    Monitor webhook deliveries
  </Card>

  <Card color="#0080FF" title="Collections Guide" icon="book" href="/guides/collections">
    Handle payment webhooks
  </Card>

  <Card color="#0080FF" title="Payouts Guide" icon="book" href="/guides/payouts">
    Handle payout webhooks
  </Card>
</CardGroup>
