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

# Webhooks

> Receive real-time payment notifications

## Overview

Webhooks notify your server when payment events occur. Set the `webhookUrl` when creating a payment, and Ebioro will POST to that URL whenever the payment status changes.

## Webhook Payload

```json theme={null}
{
  "data": {
    "type": "transaction_updated",
    "id": "pt_ABC123",
    "status": "paid",
    "settlement_status": "paid",
    "amount": {
      "value": 1000,
      "currency": "USD"
    },
    "settlement_amount": 1000,
    "settlement_currency": "USDC",
    "settlement_fee": 25,
    "amount_paid": 1000,
    "amount_received": 1000,
    "amount_remaining": 0,
    "history": [
      {
        "time": 1714124827333,
        "status": "success",
        "description": "Payment created"
      },
      {
        "time": 1714124900000,
        "status": "success",
        "description": "Customer paid"
      }
    ],
    "metadata": {}
  }
}
```

## Event Types

| Type                  | When                                              |
| --------------------- | ------------------------------------------------- |
| `transaction_created` | Payment order created                             |
| `transaction_updated` | Payment status changed (paid, underpaid, settled) |
| `transaction_failed`  | Payment processing failed                         |

<Info>
  A payment may emit **several** `transaction_updated` events as it progresses (for example `underpaid` then `paid`), and the number can vary by settlement type. Don't assume a fixed count — drive your logic off the latest `status` and `settlement_status` in each event, and treat `settlement_status: "paid"` as the signal that funds have settled to your account. Deduplicate by payment `id`.
</Info>

## Verifying Webhooks

Every webhook includes an `X-WEBHOOK-AUTH` header containing an HMAC-SHA256 signature. **Always verify this signature** before processing the webhook.

The signature is computed over the **raw request body** exactly as sent. Verify against those raw bytes — do not parse the JSON and re-serialize it, because a re-serialized object can differ in key order or whitespace and the signature will no longer match.

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

// Capture the raw body for the webhook route (do NOT use express.json() here).
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-auth'];
  const rawBody = req.body; // Buffer — the exact bytes Ebioro signed

  const expected = crypto
    .createHmac('sha256', YOUR_API_SECRET_KEY)
    .update(rawBody)
    .digest('hex');

  const valid =
    signature &&
    expected.length === signature.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

  if (!valid) {
    return res.status(401).send('Invalid signature');
  }

  const { type, status, id } = JSON.parse(rawBody.toString()).data;
  // Process the webhook...

  res.status(200).send('OK');
});
```

## Requirements

* Webhook URL must use **HTTPS** in production
* Your endpoint must respond with a **200** status code
* Respond within **10 seconds** or the request will time out

## Retry Policy

If your endpoint returns a non-200 status code or times out, the webhook is **not retried automatically**. Use the [delivery log](#delivery-logs-and-resend) to find failed deliveries and resend them.

## Delivery Logs and Resend

Every webhook delivery is recorded — successful or not. Use the [Webhook Logs API](/api-reference/webhook-logs/list-webhook-logs) to audit and recover:

```bash theme={null}
# Find failed deliveries for a payment
GET /merchants/{merchantId}/webhook-logs?status=failed&checkout_id=pt_ABC123

# Inspect one delivery — includes the exact payload sent and your endpoint's response
GET /merchants/{merchantId}/webhook-logs/{id}

# Replay it to your endpoint
POST /merchants/{merchantId}/webhook-logs/{id}/resend
```

Each log entry records the event type, target URL, delivery status (`success` | `failed` | `skipped`), HTTP status, response time, and attempt count.

A **resend** replays the originally stored payload, re-signed with your **current** secret key:

* The payload is byte-identical to the original delivery — your endpoint receives the same event again. **Deduplicate by event content** (payment id + status), not by signature.
* Resends are rate-limited to **10 per minute**.

<Warning>
  Always verify the `X-WEBHOOK-AUTH` signature. Without verification, an attacker could send fake webhook events to your endpoint.
</Warning>
