Skip to main content
Webhooks notify your system instantly when a payment status changes.

Webhook Events

EventDescription
payment.createdPayment request created
payment.processingCustomer is completing payment
payment.completedPayment successful
payment.failedPayment failed or rejected
payment.expiredPayment expired (30 min timeout)

Webhook Payload

{
  "event": "payment.completed",
  "timestamp": "2026-01-18T14:35:00.000Z",
  "data": {
    "paymentId": "pay_abc123def456",
    "status": "completed",
    "amount": 149.99,
    "currency": "DKK",
    "referenceNumber": "ORDER-12345",
    "paidAt": "2026-01-18T14:35:00.000Z",
    "metadata": {
      "storeId": "STORE-001"
    }
  }
}

Webhook Headers

HeaderDescription
X-AcountPay-TimestampUnix timestamp when sent
X-AcountPay-SignatureHMAC-SHA256 signature

Verifying Signatures

Always verify webhook signatures to ensure requests are from AcountPay.
const crypto = require('crypto');

function verifyWebhook(req, webhookSecret) {
  const timestamp = req.headers['x-acountpay-timestamp'];
  const signature = req.headers['x-acountpay-signature'];
  const payload = JSON.stringify(req.body);

  // Check timestamp is within 5 minutes
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) throw new Error('Timestamp too old');

  // Verify signature
  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error('Invalid signature');
  }
  return true;
}

// Express.js handler
app.post('/webhooks/acountpay', express.json(), (req, res) => {
  try {
    verifyWebhook(req, process.env.WEBHOOK_SECRET);
    
    if (req.body.event === 'payment.completed') {
      markOrderPaid(req.body.data.referenceNumber);
    }
    
    res.sendStatus(200);
  } catch (error) {
    res.sendStatus(401);
  }
});

Response Requirements

Your webhook endpoint must respond with HTTP 200 within 10 seconds.
ResponseResult
200, 201, 204Success
5xx or timeoutWill retry

Retry Policy

Failed webhooks retry with exponential backoff:
AttemptDelay
1Immediate
21 second
32 seconds
44 seconds
58 seconds

Best Practices

  1. Always verify signatures - Prevents spoofing attacks
  2. Respond quickly - Process asynchronously, return 200 immediately
  3. Handle duplicates - Use paymentId for idempotency
  4. Use HTTPS - Required in production