Skip to main content

Webhooks

Webhooks allow your server to receive real-time notifications when a payment is completed. This is the recommended way to confirm payments in production, as it provides reliable server-to-server communication.

How Webhooks Work

When a customer completes a payment through the SmartPay button:

  1. ApoloPay sends an HTTP POST request to your configured webhook URL
  2. Your server receives the request with the payment details
  3. Your server verifies the webhook authenticity using the webhook secret
  4. Your server processes the payment confirmation (e.g., updates the order status)

Setting Up Webhooks

  1. Go to the ApoloPay Dashboard
  2. Navigate to your payment button's settings
  3. In the Webhook section, enter your endpoint URL
  4. Save — a Webhook Secret will be generated for you
tip

Your webhook endpoint must be publicly accessible via HTTPS. During development, you can use tools like ngrok to expose your local server.

Webhook Payload

When a payment is completed, ApoloPay sends a POST request to your webhook URL with the following structure:

{
"event": "payment.completed",
"processId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amount": 25.50,
"metadata": {
"orderId": "ORD-9821",
"customerEmail": "[email protected]"
},
"timestamp": "2026-03-19T12:00:00Z"
}

Handling Webhooks

Node.js (Express)

const express = require('express');
const crypto = require('crypto');
const app = express();

const WEBHOOK_SECRET = process.env.APOLOPAY_WEBHOOK_SECRET;

// Use raw body for signature verification
app.post(
'/api/webhooks/apolopay',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-apolopay-signature'];
const payload = req.body.toString();

// Verify the webhook signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');

if (signature !== expectedSignature) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}

// Process the webhook
const event = JSON.parse(payload);
console.log('Payment completed:', event.processId);

// Update your order status in the database
// ...

// Respond with 200 to acknowledge receipt
res.status(200).json({ received: true });
}
);

Python (FastAPI)

import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException
import os

app = FastAPI()

WEBHOOK_SECRET = os.getenv("APOLOPAY_WEBHOOK_SECRET")

@app.post("/api/webhooks/apolopay")
async def handle_webhook(request: Request):
payload = await request.body()
signature = request.headers.get("x-apolopay-signature")

# Verify the webhook signature
expected_signature = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256,
).hexdigest()

if signature != expected_signature:
raise HTTPException(status_code=401, detail="Invalid signature")

# Process the webhook
event = await request.json()
print(f"Payment completed: {event['processId']}")

# Update your order status in the database
# ...

return {"received": True}

Best Practices

PracticeDescription
Always verify signaturesUse the webhook secret to validate that requests are from ApoloPay
Respond quicklyReturn a 200 status code promptly. Process heavy logic asynchronously
Handle duplicatesYour endpoint should be idempotent — the same event may be sent more than once
Use HTTPSAlways use HTTPS for your webhook endpoint in production
Log eventsKeep a log of received webhook events for debugging and auditing

Retry Policy

If your server doesn't respond with a 2xx status code, ApoloPay will retry the webhook delivery with exponential backoff.

Next Steps