Skip to main content

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 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

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();
}
Always use a constant-time comparison function (timingSafeEqual, compare_digest, hash_equals) to prevent timing attacks. Never use === or == to compare signatures.

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.
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.