V2 signing verifies webhook authenticity with a single HMAC-SHA256 signature covering the entire request body, replacing the per-field signatureHash used in V1.


How it works

Generating a signing secret switches all webhook deliveries to V2. Here is what changes:

V1 (Legacy)V2 (Signing Secret)
Signature locationsignatureHash field inside the JSON bodyX-Atoa-Signature HTTP header
What is signedorderId | paymentRequestIdThe entire JSON request body
AlgorithmHMAC-SHA256HMAC-SHA256
Extra body fieldssignatureHash, signature presenteventType added; signatureHash and signature removed

When Atoa delivers a webhook to a V2-enabled endpoint:

  1. The signatureHash and signature fields are removed from the body.
  2. An eventType field is added to the body (e.g. "PAYMENTS_STATUS", "POS_PAYMENT_STATUS").
  3. The full JSON body is signed: HMAC-SHA256(signingSecret, requestBody).
  4. The signature is sent in the X-Atoa-Signature header as v1=<hex-encoded hash>.

Getting started

1

Generate a signing secret

Go to the Atoa Dashboard and navigate to Settings → Webhooks. Click Generate a signing key. You will be asked to confirm your password.

Once generated, the secret is displayed once in the format:

whsec_<base64-encoded value>

Copy and store it securely. You will not be able to view it again.

2

Store the secret securely

Store the signing secret in an environment variable or a secrets manager. Never hard-code it in your source code or commit it to version control.

3

Verify signatures on your server

For every incoming webhook request, compute the expected signature and compare it to the value in the X-Atoa-Signature header. See the verification examples below.


Verifying the signature

To verify an incoming webhook:

  1. Extract the signature — Read the X-Atoa-Signature header value and strip the v1= prefix to get the received hex signature.
  2. Decode your signing secret — The secret is in the format whsec_<base64-encoded key>. Strip the whsec_ prefix and base64-decode the remaining value to get the raw signing key bytes.
  3. Compute the expected signature — Calculate HMAC-SHA256(key = decoded secret bytes, data = raw request body) and hex-encode the result.
  4. Compare signatures — Use a timing-safe comparison to check the received signature against the expected signature. If they match, the webhook is authentic.
const crypto = require('crypto');

function verifyWebhookSignature(signingSecret, rawBody, signatureHeader) {
  // signatureHeader is the value of X-Atoa-Signature, e.g. "v1=abc123..."
  const receivedSig = signatureHeader.replace('v1=', '');

  // Strip whsec_ prefix and base64-decode to get raw signing key
  const secret = Buffer.from(signingSecret.split('_')[1], 'base64');

  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig, 'hex'),
    Buffer.from(expectedSig, 'hex')
  );
}

// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-atoa-signature'];
  const rawBody = req.body.toString('utf8');

  if (!verifyWebhookSignature(process.env.ATOA_SIGNING_SECRET, rawBody, signature)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(rawBody);
  // Process the event...
  res.status(200).send('OK');
});

Always use a timing-safe comparison function (such as crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) when comparing signatures. This prevents timing attacks that could allow an attacker to guess the signature byte by byte.


Key rotation

You can rotate your signing secret at any time from the Atoa Dashboard under Settings → Webhooks. Rotation requires password confirmation.

When you rotate:

  • A new secret is generated and displayed once — copy it immediately.
  • The previous secret is immediately invalidated. All subsequent deliveries use the new secret.
  • Update the secret in your server environment before confirming the rotation to avoid failed verifications.

Backward compatibility

V2 signing is opt-in per merchant. Without a signing secret, webhooks continue in V1 format with signatureHash and signature fields. Generating a secret switches your deliveries to V2 without affecting other merchants.

Subscribing to the POS_PAYMENT_STATUS event type requires a V2 signing secret. You must generate a signing secret before you can create a POS_PAYMENT_STATUS webhook subscription.

Generating a signing secret switches all webhook deliveries to V2 — including those sent to your WooCommerce or Magento plugin. To keep plugin webhooks working:

  1. Update the Atoa plugin to the latest version.
  2. Paste your signing secret into the plugin’s webhook signing key field and save.

See the WooCommerce or Magento setup guide for where to find this setting.