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

# Set Up Webhooks for Payment Notifications

> Learn how to register a webhook endpoint, verify payload signatures, and handle real-time payment event notifications from IteraPay.

Webhooks allow IteraPay to push real-time notifications to your server whenever a payment event occurs — such as an invoice being paid or a transaction being confirmed. Rather than polling the API, you register an endpoint on your server and IteraPay sends HTTP POST requests to it as events happen. This guide covers how to create your webhook, verify its authenticity, and handle incoming event payloads.

<Warning>
  Always verify the webhook signature on every incoming request. Without signature verification, your endpoint is vulnerable to spoofed payloads from unauthorized sources.
</Warning>

## Prerequisites

* A publicly accessible HTTPS endpoint on your server
* Your `merchantID` and a valid API key or JWT bearer token

***

## Register a webhook endpoint

<Steps>
  <Step title="Create or update your webhook">
    Register your endpoint URL and a secret that IteraPay will use to sign payloads. If a webhook already exists for your merchant account, this request will update it.

    ```bash theme={null}
    curl --request POST \
      --url 'https://api.iterapay.com/weebhook/{merchantID}' \
      --header 'Authorization: Bearer YOUR_JWT_TOKEN' \
      --header 'Content-Type: application/json' \
      --data '{
        "url": "https://yoursite.com/webhooks/iterapay",
        "secret": "your-webhook-secret-min-32-chars-long"
      }'
    ```

    **Expected response:**

    ```json theme={null}
    {
      "token": "whk_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    ```

    <Note>
      Store the returned `token` securely. You'll use it alongside your `secret` to verify that incoming webhook payloads genuinely originate from IteraPay.
    </Note>
  </Step>

  <Step title="Verify your webhook configuration">
    Confirm that your webhook is registered correctly by retrieving the current configuration.

    ```bash theme={null}
    curl --request GET \
      --url 'https://api.iterapay.com/weebhook/{merchantID}' \
      --header 'Authorization: Bearer YOUR_JWT_TOKEN'
    ```

    **Expected response:**

    ```json theme={null}
    {
      "url": "https://yoursite.com/webhooks/iterapay",
      "createdAt": "2024-01-01T10:00:00Z",
      "updatedAt": "2024-01-01T10:00:00Z"
    }
    ```
  </Step>

  <Step title="Implement your webhook handler">
    Set up an HTTP endpoint on your server to receive `POST` requests from IteraPay. Your handler must:

    1. Read the raw request body (before any JSON parsing)
    2. Verify the signature (see the next section)
    3. Process the event
    4. Return a `200` status code promptly

    <Tabs>
      <Tab title="Node.js">
        ```javascript webhook-handler.js theme={null}
        const express = require('express');
        const crypto = require('crypto');
        const app = express();

        // Use raw body parser to preserve the original payload for signature verification
        app.use('/webhooks/iterapay', express.raw({ type: 'application/json' }));

        app.post('/webhooks/iterapay', (req, res) => {
          const signature = req.headers['x-iterapay-signature'];
          const rawBody = req.body;

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

          const event = JSON.parse(rawBody);

          switch (event.type) {
            case 'invoice.paid':
              // Handle paid invoice
              break;
            case 'transaction.confirmed':
              // Handle confirmed transaction
              break;
            default:
              console.log(`Unhandled event type: ${event.type}`);
          }

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

        function verifySignature(payload, signature) {
          const hmac = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET);
          const computed = hmac.update(payload).digest('hex');
          return crypto.timingSafeEqual(
            Buffer.from(computed),
            Buffer.from(signature)
          );
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python webhook_handler.py theme={null}
        import hmac
        import hashlib
        from flask import Flask, request, abort

        app = Flask(__name__)
        WEBHOOK_SECRET = 'your-webhook-secret-min-32-chars-long'

        @app.route('/webhooks/iterapay', methods=['POST'])
        def handle_webhook():
            signature = request.headers.get('X-Iterapay-Signature')
            raw_body = request.get_data()

            if not verify_signature(raw_body, signature):
                abort(401)

            event = request.get_json(force=True)
            event_type = event.get('type')

            if event_type == 'invoice.paid':
                # Handle paid invoice
                pass
            elif event_type == 'transaction.confirmed':
                # Handle confirmed transaction
                pass

            return 'OK', 200

        def verify_signature(payload, signature):
            computed = hmac.new(
                WEBHOOK_SECRET.encode(),
                payload,
                hashlib.sha256
            ).hexdigest()
            return hmac.compare_digest(computed, signature)
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

***

## Verify webhook signatures

Every request from IteraPay includes a signature header that you must validate before processing the payload. This confirms the request is genuine and the payload has not been tampered with.

The signature is an HMAC-SHA256 hash of the raw request body, signed using your webhook secret.

**Verification steps:**

1. Extract the `X-Iterapay-Signature` header value
2. Compute `HMAC-SHA256(rawBody, yourSecret)`
3. Compare the computed hash to the header value using a constant-time comparison function (e.g., `timingSafeEqual` in Node.js or `hmac.compare_digest` in Python)
4. Reject the request if the values do not match

<Warning>
  Use a constant-time comparison function to prevent timing attacks. Do not use `===` or `==` to compare the signature strings directly.
</Warning>

***

## Example webhook payload

IteraPay sends a JSON payload to your endpoint for each event. The structure includes an event `type` and an associated `data` object.

```json theme={null}
{
  "type": "invoice.paid",
  "merchantID": "merchant_e5f6a7b8-c9d0-1234-efab-345678901234",
  "timestamp": "2024-01-01T11:30:00Z",
  "data": {
    "invoiceID": "inv_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "amount": "100.500000",
    "currency": "USDT",
    "paidAt": "2024-01-01T11:30:00Z",
    "txHash": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12345678"
  }
}
```

### Common event types

| Event type              | When it fires                                                        |
| ----------------------- | -------------------------------------------------------------------- |
| `invoice.paid`          | A customer sends payment to an invoice address                       |
| `transaction.confirmed` | An on-chain transaction reaches the required number of confirmations |
| `collection.completed`  | A fund collection sweep finishes                                     |
| `withdrawal.completed`  | A withdrawal transaction is confirmed on-chain                       |

***

## Remove a webhook

To stop receiving webhook notifications, delete your webhook configuration:

```bash theme={null}
curl --request DELETE \
  --url 'https://api.iterapay.com/weebhook/{merchantID}' \
  --header 'Authorization: Bearer YOUR_JWT_TOKEN'
```

A successful deletion returns a `200` or `204` response with no body.

***

## Next steps

Now that you're receiving payment events in real time, you can automate your order fulfillment, update your database, or trigger downstream workflows on each payment confirmation.
