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

# Webhooks

> Receive real-time notifications for events

StablePay sends webhooks to notify you of events in real-time.

## Setup

1. Configure your webhook URL in the **Dashboard**
2. Set a webhook secret for signature verification
3. Ensure your endpoint returns `200 OK` quickly

## Webhook Format

All webhooks follow this structure:

```json theme={null}
{
  "event": "transaction.payout_completed",
  "timestamp": "2025-06-15T10:30:00.000Z",
  "partnerId": "your_partner_id",
  "data": {
    // Event-specific payload
  }
}
```

### Payout Completed Example

```json theme={null}
{
  "event": "transaction.payout_completed",
  "timestamp": "2025-06-15T10:30:00.000Z",
  "partnerId": "ss_api_partner_production",
  "data": {
    "transactionId": "txn_1768722777_abc123",
    "merchant_id": "your_merchant_reference",
    "payoutId": "NKSHV2beTTiymZY64S1DWMcxYDP8",
    "orderId": "order_1768722781916_fa33d9127b22",
    "amount": 50000,
    "currency": "INR",
    "status": "SUCCESS",
    "utr": "601813168575",
    "provider": "TSP-1",
    "receiver_name": "JOHN DOE",
    "bank_account_masked": "9240****5350",
    "ifsc_code": "UTIB0005616",
    "completedAt": "2025-06-15T10:30:00.000Z",
    "message": "Payout completed successfully"
  }
}
```

## Event Types

### User Events

| Event              | Trigger              |
| ------------------ | -------------------- |
| `user.created`     | User created via API |
| `user.kyc_updated` | KYC status changed   |

### Transaction Events

| Event                           | Trigger                                  |
| ------------------------------- | ---------------------------------------- |
| `transaction.created`           | Transaction created                      |
| `transaction.deposit_detected`  | Deposit seen on blockchain               |
| `transaction.deposit_confirmed` | Required confirmations met               |
| `transaction.sweep_confirmed`   | Pool settlement sweep confirmed on-chain |
| `transaction.sweep_failed`      | Pool settlement sweep failed             |
| `transaction.payout_initiated`  | Bank transfer started                    |
| `transaction.payout_completed`  | Payout successful                        |
| `transaction.payout_failed`     | Payout failed                            |

## Signature Verification

Verify webhook signatures to ensure authenticity:

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

function verifyWebhookSignature(payload, signature, secret) {
  // Parse: t=timestamp,v1=signature
  const [timestampPart, signaturePart] = signature.split(',');
  const timestamp = timestampPart.replace('t=', '');
  const receivedSig = signaturePart.replace('v1=', '');

  // Check timestamp (5 min tolerance)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error('Webhook timestamp too old');
  }

  // Compute expected signature
  const message = `${timestamp}.${payload}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  // Compare
  if (!crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(receivedSig)
  )) {
    throw new Error('Invalid webhook signature');
  }

  return true;
}

// Express example
app.post('/webhooks/stablepay', (req, res) => {
  const signature = req.headers['x-stablepay-signature'];
  const payload = JSON.stringify(req.body);

  try {
    verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET);

    // Process webhook
    const { event, data } = req.body;
    handleWebhookEvent(event, data);

    res.status(200).json({ received: true });
  } catch (error) {
    res.status(401).json({ error: error.message });
  }
});
```

## Retry Policy

If your endpoint doesn't return `200-299`:

| Attempt | Delay     |
| ------- | --------- |
| 1       | Immediate |
| 2       | 1 second  |
| 3       | 2 seconds |
| 4       | 4 seconds |
| 5       | 8 seconds |

After 5 failures, the webhook is marked as failed.

## Manual Retry

Retry failed webhooks via API:

```bash theme={null}
curl -X POST https://api.stablepay.global/v2/webhooks/{eventId}/retry \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Best Practices

<AccordionGroup>
  <Accordion title="Respond quickly">
    Return 200 immediately, then process async. We timeout after 30 seconds.
  </Accordion>

  <Accordion title="Handle duplicates">
    Webhooks may be delivered multiple times. Use eventId for deduplication.
  </Accordion>

  <Accordion title="Verify signatures">
    Always verify webhook signatures in production.
  </Accordion>

  <Accordion title="Log everything">
    Log webhook payloads for debugging and audit trails.
  </Accordion>
</AccordionGroup>

## Testing Webhooks

Use the Dashboard to send test webhooks to your endpoint.
