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

# Webhooks

> Signed outbound merchant webhook events from NexaPay

# Webhooks

NexaPay sends signed outbound webhooks to the merchant `webhookUrl` whenever merchant account events complete.

## How Signing Works

* NexaPay signs each webhook payload with the merchant's `webhookSecret`
* Merchants verify the signature before trusting the payload
* Signature verification should happen in your backend, not in browser code

### Headers

NexaPay includes headers similar to:

* `x-nexapay-signature`
* `x-nexapay-timestamp`
* `x-nexapay-event`
* `x-nexapay-event-id`
* `x-nexapay-business-id`

### Signature Scheme

The signature is computed over the raw body plus timestamp using HMAC SHA-256.

## Webhook Events

### `deposit.received`

Sent when money is credited to a merchant virtual account or business account.

```json theme={null}
{
  "event": "deposit.received",
  "eventId": "evt_01JABC123XYZ",
  "businessId": "66f1234567890abcde123456",
  "businessName": "Acme Stores",
  "environment": "prod",
  "occurredAt": "2026-05-07T10:15:30.000Z",
  "data": {
    "transactionId": "20260507101530abc",
    "reference": "PAY_20260507_001",
    "accountNumber": "9123456789",
    "amount": 50000,
    "senderName": "John Doe",
    "senderAccount": "0123456789",
    "senderBank": "GTBank",
    "balanceAfter": 125000,
    "status": "successful",
    "merchantCustomerId": "cust_12345",
    "merchantReference": "order_90881",
    "metadata": {
      "purpose": "wallet_topup",
      "source": "checkout"
    }
  }
}
```

### `withdrawal.completed`

Sent when a merchant withdrawal settles successfully.

```json theme={null}
{
  "event": "withdrawal.completed",
  "eventId": "evt_01JWXYZ456ABC",
  "businessId": "66f1234567890abcde123456",
  "businessName": "Acme Stores",
  "environment": "prod",
  "occurredAt": "2026-05-07T12:00:00.000Z",
  "data": {
    "transactionId": "20260507115959xyz",
    "reference": "20260507115959xyz",
    "amount": 100000,
    "fee": 2050,
    "totalDebit": 102050,
    "bankAccount": "0123456789",
    "bankName": "GTBank",
    "providerReference": "090110260507120000123456789012",
    "status": "successful",
    "metadata": {
      "purpose": "payout",
      "batchId": "batch_7781"
    }
  }
}
```

### `withdrawal.failed`

Sent when a merchant withdrawal does not complete.

```json theme={null}
{
  "event": "withdrawal.failed",
  "eventId": "evt_01JWXYZ789DEF",
  "businessId": "66f1234567890abcde123456",
  "businessName": "Acme Stores",
  "environment": "prod",
  "occurredAt": "2026-05-07T12:05:00.000Z",
  "data": {
    "transactionId": "20260507120459uvw",
    "reference": "20260507120459uvw",
    "amount": 100000,
    "fee": 2050,
    "totalDebit": 102050,
    "bankAccount": "0123456789",
    "bankName": "GTBank",
    "providerReference": null,
    "status": "failed",
    "failureReason": "Insufficient beneficiary balance",
    "metadata": {
      "purpose": "payout",
      "batchId": "batch_7781"
    }
  }
}
```

## Merchant Webhook Secret

NexaPay exposes the merchant webhook secret from the dashboard for authenticated users.

### Reveal Secret

`GET /business/webhook-secret`

Returns the current webhook secret so the merchant can verify signatures.

### Regenerate Secret

`POST /business/webhook-secret/regenerate`

Rotates the secret and returns the new value. Merchants should update their backend immediately after regenerating.

## Delivery Rules

* Webhooks are sent after the relevant transaction is committed.
* Delivery is best-effort with retries.
* Merchants should deduplicate using `eventId`.
* Merchants should verify the signature before processing the payload.
