V2 Webhook Signing
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 location | signatureHash field inside the JSON body | X-Atoa-Signature HTTP header |
| What is signed | orderId | paymentRequestId | The entire JSON request body |
| Algorithm | HMAC-SHA256 | HMAC-SHA256 |
| Extra body fields | signatureHash, signature present | eventType added; signatureHash and signature removed |
When Atoa delivers a webhook to a V2-enabled endpoint:
- The
signatureHashandsignaturefields are removed from the body. - An
eventTypefield is added to the body (e.g."PAYMENTS_STATUS","POS_PAYMENT_STATUS"). - The full JSON body is signed:
HMAC-SHA256(signingSecret, requestBody). - The signature is sent in the
X-Atoa-Signatureheader asv1=<hex-encoded hash>.
Getting started
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.
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.
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:
- Extract the signature — Read the
X-Atoa-Signatureheader value and strip thev1=prefix to get the received hex signature. - Decode your signing secret — The secret is in the format
whsec_<base64-encoded key>. Strip thewhsec_prefix and base64-decode the remaining value to get the raw signing key bytes. - Compute the expected signature — Calculate
HMAC-SHA256(key = decoded secret bytes, data = raw request body)and hex-encode the result. - 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:
- Update the Atoa plugin to the latest version.
- 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.