AventopayAventopay

Webhooks — the only source of truth

How to receive, verify and process Aventopay callbacks reliably.

Absolute rule
The webhook is the only source of truth. Never credit / activate / ship a customer based on the return_url — a user can manipulate the URL. Always wait for the signed callback.

Overview

After each payment event, Aventopay sends a POST to your callback_url with a JSON payload signed HMAC-SHA256. You reply 200 OK to acknowledge; any other code triggers a retry.

Emitted statuses

statusDescriptionTypical action
successPayment confirmed and funds credited.Fulfill order / grant access.
pendingIn progress (3DS, processing) — another callback will follow.Mark as pending, do not debit anything.
cancelledPayment cancelled by customer or expired.Cleanup, notify UX.

Payload

{
  "event": "payment.updated",
  "status": "success",
  "transaction_id": "TXN_12345",
  "external_user_id": "client_123",
  "amount": 5000,
  "currency": "EUR",
  "provider_reference": "apy_ref_a1b2c3d4e5f6",
  "occurred_at": "2026-07-22T14:23:11Z"
}

Verify the signature

The X-Aventopay-Signature header contains the HMAC-SHA256 of the raw body, signed with your webhook_secret. Verify it before any JSON.parse — otherwise you're letting forged payloads through.

import crypto from 'crypto';

function verifyAventopaySignature(rawBody, signatureHeader, webhookSecret) {
  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody, 'utf8')
    .digest('hex');
  const a = Buffer.from(signatureHeader, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Idempotency

Aventopay retries on error: your handler must be idempotent. Use provider_reference as unique key — it's stable across the transaction's lifetime.

-- Simple SQL idempotency table
CREATE TABLE processed_webhooks (
  reference VARCHAR(64) PRIMARY KEY,
  processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- In the handler:
INSERT INTO processed_webhooks (reference) VALUES (?)
ON DUPLICATE KEY UPDATE reference = reference;

-- If the row already existed → skip the processing.

Retry queue

If your server does not respond 2xx, we retry with exponential backoff:

AttemptDelay from 1st
1Immediate
2+30 s
3+2 min
4+10 min
5+30 min
6+1 h
7+2 h
8+6 h
Abandonment after 12 h

Test locally

Use a temporary HTTPS tunnel (ngrok, cloudflared) to expose your local server. Configure callback_url with the tunnel URL. Events arrive on your localhost in real time.

Replay from the dashboard

From the Transactions view of the dashboard, a Replay webhook button lets you manually resend a callback if your system was unavailable.

Resources