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

# Webhooks & Events

> Receive real-time notifications for payment events

# Webhook Integration & Security

Webhooks allow your application to receive real-time asynchronous notifications whenever a payment is approved, debited, or settled.

## Webhook Signature Verification (`HMAC-SHA512`)

Every webhook request sent by Handle includes the `X-Handle-Signature` header:

```http theme={null}
X-Handle-Signature: t=1726900000,v1=5a8e7d6c5b4a3f2e1...
```

* `t`: UNIX timestamp (in seconds) of when the webhook was generated.
* `v1`: `HMAC-SHA512(t + "." + rawBody, webhookSigningSecret)`.

### Node.js / TypeScript Verification Code

```typescript theme={null}
import crypto from 'crypto';

export function verifyHandleWebhook(
  rawBody: string,
  signatureHeader: string,
  webhookSecret: string
): boolean {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );
  const timestamp = parts.t;
  const signature = parts.v1;

  // 1. Prevent replay attacks (reject if older than 5 minutes)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
    return false;
  }

  // 2. Compute expected signature
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha512', webhookSecret)
    .update(signedPayload)
    .digest('hex');

  // 3. Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

***

## Event Types

### `charge.success`

Triggered immediately when the customer approves the payment and bank debit is confirmed:

```json theme={null}
{
  "id": "evt_98f2a1b3c4d5",
  "event": "charge.success",
  "createdAt": "2026-09-21T08:25:00Z",
  "data": {
    "reference": "CHEF-LOLA-ORD-1092",
    "amount": 1250000,
    "currency": "NGN",
    "fee": 12500,
    "netAmount": 1237500,
    "status": "SUCCESS",
    "channel": "handle_push",
    "customer": {
      "handle": "@ayodeji",
      "phone": "08012345678",
      "name": "Ayodeji Peters"
    },
    "metadata": {
      "table": 4
    },
    "paidAt": "2026-09-21T08:24:58Z"
  }
}
```

***

## Exponential Backoff Retry Policy

Handle will never flood or spam your server during outages. If your server returns anything other than `200 OK`, Handle retries using an exponential schedule:

* **Attempt 1:** Immediate ($T=0$)
* **Attempt 2:** $T+5\text{ mins}$
* **Attempt 3:** $T+30\text{ mins}$
* **Attempt 4:** $T+2\text{ hours}$
* **Attempt 5:** $T+8\text{ hours}$
* **Attempt 6 (Final):** $T+24\text{ hours}$

***

## Manual Replay in Dashboard

You can manually re-send any past webhook event with 1-click in the **Handle Merchant Dashboard → Webhook Logs**.
