V1 signing verifies webhook authenticity using a signatureHash field in the JSON body, signing only the orderId combined with paymentRequestId (payments) or refundId (refunds).

V2 signing is recommended for all new integrations. V2 signs the entire request body and uses a dedicated signing secret. See the V2 Webhook Signing guide for setup instructions.


How it works

Details
Signature locationsignatureHash field in the JSON body
What is signedorderId | paymentRequestId (payments) or orderId | refundId (refunds)
AlgorithmHMAC-SHA256
KeyYour Atoa API secret (the same secret used for API authentication)

Payment signature verification

To verify a payment webhook:

  1. Retrieve the orderId from your server — the one you passed when creating the payment request (not atoaOrderId).
  2. Get the paymentRequestId from the webhook payload.
  3. Compute HMAC-SHA256(orderId + "|" + paymentRequestId, atoaSecret).
  4. Compare the result against the signatureHash field in the payload.
const crypto = require('crypto');

function verifyV1Signature(orderId, paymentRequestId, atoaSecret, signatureHash) {
  const data = orderId + '|' + paymentRequestId;
  const generatedSignature = crypto
    .createHmac('sha256', atoaSecret)
    .update(data)
    .digest('hex');

  return generatedSignature === signatureHash;
}

// Usage
app.post('/webhook', express.json(), (req, res) => {
  const { orderId, paymentRequestId, signatureHash } = req.body;
  const yourOrderId = getOrderIdFromYourServer(); // Your original orderId, not atoaOrderId

  if (!verifyV1Signature(yourOrderId, paymentRequestId, process.env.ATOA_SECRET, signatureHash)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event...
  res.status(200).send('OK');
});

Refund signature verification

For refund webhooks, the signature uses refundId instead of paymentRequestId:

hmac_sha256(orderId + "|" + refundId, atoaSecret)

The verification logic is identical — just substitute paymentRequestId with refundId from the refund webhook payload.

Node.js
function verifyRefundSignature(orderId, refundId, atoaSecret, signatureHash) {
  const data = orderId + '|' + refundId;
  const generatedSignature = crypto
    .createHmac('sha256', atoaSecret)
    .update(data)
    .digest('hex');

  return generatedSignature === signatureHash;
}

V2 signing improves on V1 in several ways:

  • Full-body coverage — V2 signs the entire request body, protecting all fields in the payload.
  • Dedicated signing secret — V2 uses a separate signing secret rather than your API key, so your credentials stay compartmentalised.
  • POS supportPOS_PAYMENT_STATUS webhooks require V2 signing.

Migration to V2

To migrate from V1 to V2 signing:

  1. Generate a signing secret from the Atoa Dashboard under Settings → Webhooks.
  2. Update your webhook verification logic to use the X-Atoa-Signature header instead of the signatureHash body field.
  3. Once a signing secret is generated, the signatureHash and signature fields are removed from webhook payloads and an eventType field is added.

See the V2 Webhook Signing guide for full setup instructions and code samples.

V1 and V2 cannot be used simultaneously for the same merchant. Generating a signing secret switches all your webhook deliveries to V2.