Skip to main content

Creating a Webhook Listener

Implement a webhook listener to receive real-time event notifications from WickiePay.

Requirements

Your webhook endpoint must:

  • Accept HTTP POST requests
  • Return 200 OK within 30 seconds
  • Be accessible via HTTPS (TLS required)
  • Validate the webhook signature

Node.js Example

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

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.WICKIEPAY_WEBHOOK_SECRET;

// Verify webhook signature
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

app.post('/webhooks/wickiepay', (req, res) => {
const signature = req.headers['x-webhook-signature'];

// 1. Verify signature
if (!verifySignature(req.body, signature)) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}

// 2. Process the event
const { event, data } = req.body;

switch (event) {
case 'payment.completed':
handlePaymentCompleted(data);
break;
case 'payment.expired':
handlePaymentExpired(data);
break;
case 'channel.payment.confirmed':
handleChannelDeposit(data);
break;
default:
console.log(`Unhandled event: ${event}`);
}

// 3. Return 200 OK
res.status(200).send('OK');
});

function handlePaymentCompleted(data) {
console.log(`Payment ${data.uuid} completed for ${data.reference}`);
// Update your order status, credit customer, etc.
}

function handlePaymentExpired(data) {
console.log(`Payment ${data.uuid} expired for ${data.reference}`);
// Mark order as expired, notify customer
}

function handleChannelDeposit(data) {
console.log(`Channel deposit confirmed: ${data.uuid}`);
// Credit customer balance
}

app.listen(3000, () => console.log('Webhook listener running on port 3000'));

Python Example

from flask import Flask, request, jsonify
import hmac
import hashlib
import json

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['WICKIEPAY_WEBHOOK_SECRET']

def verify_signature(payload, signature):
expected = hmac.new(
WEBHOOK_SECRET.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)

@app.route('/webhooks/wickiepay', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_json()

if not verify_signature(payload, signature):
return 'Invalid signature', 401

event = payload['event']
data = payload['data']

if event == 'payment.completed':
# Handle completed payment
print(f"Payment {data['uuid']} completed")

return 'OK', 200

Best Practices

Idempotency

Always handle duplicate webhook deliveries gracefully. Use the uuid field to check if you've already processed an event.

  • Respond quickly — Return 200 OK before doing heavy processing. Queue the work for async processing.
  • Verify signatures — Always validate the X-Webhook-Signature header.
  • Log everything — Store raw webhook payloads for debugging and auditing.
  • Handle retries — WickiePay retries failed deliveries up to 3 times with exponential backoff.

Testing Webhooks

Use the Sandbox to generate test events, or use tools like ngrok to expose your local development server.

Next Steps