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

# Webhook Security

> Verifying webhook authenticity.

# Webhook Security

## Verifying webhooks

When you receive a webhook, verify it came from Wava before processing it. We recommend:

1. **Verify the signature** — API integrations with signing enabled receive an `X-Wava-Signature` header. See [Signature Verification](#signature-verification) below.
2. **Verify the order** — After receiving a webhook, call `GET /v1/orders/{orderId}` with your merchant key to confirm the order status matches what the webhook reported.
3. **Use HTTPS** — Always use an HTTPS endpoint for your webhook URL in production.

## Signature verification

API integrations with HMAC signing enabled receive a signature in the `X-Wava-Signature` header on every webhook request. Use it to confirm the payload has not been tampered with.

### How it works

Wava generates the signature by creating an HMAC-SHA256 hash of the JSON payload using your shared secret, then sends the hexadecimal result in the `X-Wava-Signature` header.

### Request headers

```
Content-Type: application/json
X-Wava-Event: order_payment
X-Wava-Timestamp: 2024-12-18T10:30:00.000Z
X-Wava-Signature: a1b2c3d4e5f6...
User-Agent: Wava-Webhooks/1.0
```

### Verification examples

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

    function verifyWavaWebhook(req, res, next) {
      const signature = req.headers['x-wava-signature'];

      if (!signature) {
        return res.status(401).json({ error: 'Missing signature' });
      }

      const payloadString = JSON.stringify(req.body);

      const expectedSignature = crypto
        .createHmac('sha256', process.env.WAVA_WEBHOOK_SECRET)
        .update(payloadString)
        .digest('hex');

      const isValid = crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );

      if (!isValid) {
        return res.status(401).json({ error: 'Invalid signature' });
      }

      next();
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hmac
    import hashlib
    import json
    import os

    def verify_webhook_signature(payload, signature):
        secret = os.environ['WAVA_WEBHOOK_SECRET']
        payload_string = json.dumps(payload, separators=(',', ':'))
        expected = hmac.new(
            secret.encode('utf-8'),
            payload_string.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return hmac.compare_digest(signature, expected)
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    function verifyWebhookSignature($payload, $signature) {
        $secret = $_ENV['WAVA_WEBHOOK_SECRET'];
        $payloadString = json_encode($payload, JSON_UNESCAPED_SLASHES);
        $expected = hash_hmac('sha256', $payloadString, $secret);
        return hash_equals($signature, $expected);
    }
    ```
  </Tab>
</Tabs>

<Warning>
  Always use a constant-time comparison function (`timingSafeEqual`, `compare_digest`, `hash_equals`) to prevent timing attacks. Never use `===` or `==` to compare signatures.
</Warning>

### Important notes

* The `X-Wava-Signature` is a plain hexadecimal string — no prefix like `sha256=`.
* Serialize the payload using `JSON.stringify()` with default options. Property order matters.
* Store your secret in an environment variable — never commit it to source control.
* Signature signing is optional and must be enabled in your integration configuration. Contact support if you need it enabled.

## Idempotency

Your webhook handler should be idempotent — processing the same webhook multiple times should produce the same result. Wava may send the same webhook more than once in rare cases (retries, network issues).

Use the `id_order` or `id_external` field to deduplicate incoming webhooks.

<Warning>
  Never trust webhook data alone for critical business logic (e.g., shipping an order). Always verify the order status via the API before taking action.
</Warning>
