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

# Webhooks

> Receive and verify real-time transaction notifications from Pasteaza.

Pasteaza sends webhook notifications to your configured webhook URL when important transaction events happen on your account.

Webhooks are delivered as HTTPS `POST` requests with a JSON payload.

<Info>
  Always verify the webhook signature before you trust or process the payload.
</Info>

## Configure your webhook URL

Configure your webhook endpoint in your Pasteaza merchant dashboard.

Your endpoint should be publicly accessible over HTTPS.

```http theme={null}
https://merchant.com/api/pasteaza/webhook
```

## Webhook events

Pasteaza currently supports these webhook events:

| Event                      | Description                                                                   |
| -------------------------- | ----------------------------------------------------------------------------- |
| `virtual_account.transfer` | A customer has successfully transferred funds to a generated virtual account. |
| `dedicated_account.credit` | A customer has successfully transferred funds to a dedicated account.         |
| `checkout.payment.success` | A hosted checkout payment was confirmed and credited to your Pasteaza wallet. |
| `bank_transfer.successful` | A bank transfer completed successfully.                                       |
| `bank_transfer.failed`     | A bank transfer failed.                                                       |

## Collection webhook payload

```json theme={null}
{
  "event": "virtual_account.transfer",
  "data": {
    "reference": "pstv_01JABCXYZ",
    "merchantReference": "ORDER_12345",
    "transactionReference": "psttxn_01JABCXYZ",
    "amount": 500000,
    "currency": "NGN",
    "status": "PAID",
    "accountNumber": "6020009141",
    "payerName": "John Tester",
    "payerAccountNumber": "0123456789",
    "payerBankCode": "044",
    "payerBankName": "Access Bank Plc",
    "paidAt": "2026-06-11T14:31:12Z"
  }
}
```

## Dedicated account webhook payload

```json theme={null}
{
  "event": "dedicated_account.credit",
  "data": {
    "externalReference": "CUS_001",
    "accountReference": "PZ_DCA_abc123",
    "accountNumber": "8023456789",
    "amount": 500000,
    "currency": "NGN",
    "status": "successful",
    "paidAt": "2026-06-27T10:05:00.000Z",
    "metadata": {
      "customerId": "123",
      "plan": "premium"
    }
  }
}
```

`externalReference` is the reference you supplied when creating the dedicated account. Do not expect customer name, email, phone, BVN, or NIN unless you placed those values inside `metadata`.

## Hosted checkout webhook payload

```json theme={null}
{
  "event": "checkout.payment.success",
  "data": {
    "reference": "ch_Fb8z7rX8r9w2",
    "transactionReference": "PZTXN9K3M2Q7R",
    "amount": 50000,
    "currency": "NGN",
    "customer": {
      "name": "John Doe",
      "email": "john@example.com",
      "phone": "+2348012345678"
    },
    "metadata": {
      "orderId": "ORD_123"
    },
    "paymentMethod": "bank_transfer",
    "paidAt": "2026-06-22T12:04:11.000Z",
    "timestamp": "2026-06-22T12:04:12.000Z"
  }
}
```

## Transfer success webhook payload

```json theme={null}
{
  "event": "bank_transfer.successful",
  "data": {
    "reference": "pst_trf_01JABCXYZ",
    "merchantReference": "PAYOUT_12345",
    "amount": 500000,
    "currency": "NGN",
    "status": "SUCCESSFUL",
    "recipient": {
      "recipientCode": "rcp_7hK2j9xLmQ4",
      "accountNumber": "1234567890",
      "accountName": "JOHN DOE",
      "bank": {
        "code": "044",
        "name": "Access Bank Plc"
      }
    },
    "completedAt": "2026-06-11T14:31:12Z"
  }
}
```

## Transfer failed webhook payload

```json theme={null}
{
  "event": "bank_transfer.failed",
  "data": {
    "reference": "pst_trf_01JABCXYZ",
    "merchantReference": "PAYOUT_12345",
    "amount": 500000,
    "currency": "NGN",
    "status": "FAILED",
    "reason": "Transfer failed.",
    "failedAt": "2026-06-11T14:31:12Z"
  }
}
```

## Signature verification

Each webhook request includes an `X-Pasteaza-Signature` header.

```http theme={null}
X-Pasteaza-Signature: sha256=<hex>
```

Generate an HMAC SHA-256 hash of the JSON payload with your merchant webhook secret and prefix it with `sha256=`. Compare the generated value with the value in the `X-Pasteaza-Signature` header.

Only process the webhook when both values match.

<Warning>
  Verify the exact JSON payload sent to your webhook endpoint. Do not trust a webhook payload until the signature matches.
</Warning>

## Verification flow

<Steps>
  <Step title="Receive the webhook">
    Accept the incoming HTTPS `POST` request on your webhook endpoint.
  </Step>

  <Step title="Read the signature header">
    Get the value of the `X-Pasteaza-Signature` header from the request.
  </Step>

  <Step title="Hash the raw body">
    Generate an HMAC SHA-256 hash from the raw request body using your webhook secret.
  </Step>

  <Step title="Compare signatures">
    Compare your generated hash with the signature header value.
  </Step>

  <Step title="Process the event">
    Process the webhook only after the signature is valid.
  </Step>
</Steps>

## Retry policy

If your server does not return a successful `2xx` HTTP response, Pasteaza will automatically retry delivery.

Design your webhook handler to be idempotent so repeated events do not update the same transaction more than once.

## Best practices

* Always verify the webhook signature.
* Return a `2xx` response after successful processing.
* Store processed webhook references to prevent duplicate processing.
* Fetch the related transaction from the API when you need the latest state.
* Never trust webhook payloads without signature verification.
* Keep your webhook secret secure and rotate it after any suspected exposure.

## Related guides

<CardGroup cols={2}>
  <Card title="Authentication" icon="shield-check" href="/getting-started/authentication">
    Learn how to authenticate API requests with your secret key.
  </Card>

  <Card title="Idempotency" icon="rotate" href="/getting-started/idempotency">
    Prevent duplicate processing when requests or events are retried.
  </Card>
</CardGroup>
