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:
- ApoloPay sends an HTTP
POSTrequest to your configured webhook URL - Your server receives the request with the payment details
- Your server verifies the webhook authenticity using the webhook secret
- Your server processes the payment confirmation (e.g., updates the order status)
Setting Up Webhooks
- Go to the ApoloPay Dashboard
- Navigate to your payment button's settings
- In the Webhook section, enter your endpoint URL
- 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
| Practice | Description |
|---|---|
| Always verify signatures | Use the webhook secret to validate that requests are from ApoloPay |
| Respond quickly | Return a 200 status code promptly. Process heavy logic asynchronously |
| Handle duplicates | Your endpoint should be idempotent — the same event may be sent more than once |
| Use HTTPS | Always use HTTPS for your webhook endpoint in production |
| Log events | Keep 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
- Backend Integration → — Create payment processes from your backend
- SmartPay Overview → — Review the full payment flow