Webhooks Guide
Webhooks allow your application to receive real-time notifications when events occur in your Pixlpay store. Instead of polling the API, Pixlpay sends HTTP POST requests to your endpoint.
How Webhooks Work
1. Event occurs (e.g., customer completes purchase)
2. Pixlpay creates webhook payload
3. POST request sent to your endpoint
4. Your server processes the event
5. Your server responds with 2xx statusIf your server doesn't respond or returns an error, Pixlpay automatically retries.
Available Events
Order Events
| Event | Description |
|---|---|
order.created | New order created (before payment) |
order.paid | Payment received successfully |
order.completed | Order marked as fulfilled |
order.refunded | Order was refunded |
Subscription Events
| Event | Description |
|---|---|
subscription.created | New subscription started |
subscription.renewed | Subscription renewed |
subscription.cancelled | Subscription cancelled |
Product Events
| Event | Description |
|---|---|
product.created | New product added |
product.updated | Product modified |
product.deleted | Product removed |
Creating a Webhook
Via API
curl -X POST "https://yourstore.pixlpay.net/api/external/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/pixlpay",
"events": ["order.created", "order.paid", "order.completed", "order.refunded"]
}'Response
{
"success": true,
"data": {
"id": 1,
"url": "https://yourapp.com/webhooks/pixlpay",
"events": ["order.created", "order.paid", "order.completed", "order.refunded"],
"is_active": true,
"secret": "whsec_abc123def456ghi789...",
"created_at": "2025-01-20T10:00:00Z"
}
}Save Your Secret
Store the secret value securely - you need it to verify webhook signatures.
Webhook Payload Structure
All webhooks follow this structure:
{
"id": "wh_delivery_uuid",
"event_type": "order.paid",
"created_at": "2025-01-20T14:30:00Z",
"data": {
// Event-specific payload
}
}Example: order.paid
{
"id": "wh_550e8400-e29b-41d4-a716-446655440000",
"event_type": "order.paid",
"created_at": "2025-01-20T14:30:00Z",
"data": {
"order_id": 123,
"order_number": "ORD-2025-00123",
"customer_email": "player@example.com",
"customer_name": "PlayerOne",
"total": "29.99",
"currency": "USD",
"status": "paid",
"items": [
{
"product_id": 1,
"product_name": "VIP Rank",
"quantity": 1,
"price": "29.99"
}
]
}
}Security: Signature Verification
Always verify webhook signatures to ensure requests came from Pixlpay.
Headers Sent
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC SHA-256 signature |
X-Webhook-Event | Event type |
X-Webhook-ID | Unique delivery ID |
Verification Process
- Get the raw request body (don't parse JSON first)
- Calculate HMAC SHA-256 using your secret
- Compare with the
X-Webhook-Signatureheader
PHP Example
<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
// Get raw body BEFORE parsing
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = getenv('PIXLPAY_WEBHOOK_SECRET');
if (!verifyWebhookSignature($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
// Signature valid - process the event
$event = json_decode($payload, true);
switch ($event['event_type']) {
case 'order.paid':
handleOrderPaid($event['data']);
break;
case 'order.refunded':
handleOrderRefunded($event['data']);
break;
}
http_response_code(200);
echo json_encode(['status' => 'received']);Node.js Example
const crypto = require('crypto');
const express = require('express');
const app = express();
// Use raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhooks/pixlpay', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.PIXLPAY_WEBHOOK_SECRET;
if (!verifySignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
switch (event.event_type) {
case 'order.paid':
handleOrderPaid(event.data);
break;
case 'order.refunded':
handleOrderRefunded(event.data);
break;
}
res.status(200).json({ status: 'received' });
});Python Example
import hmac
import hashlib
from flask import Flask, request
app = Flask(__name__)
def verify_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhooks/pixlpay', methods=['POST'])
def handle_webhook():
payload = request.get_data()
signature = request.headers.get('X-Webhook-Signature', '')
secret = os.environ.get('PIXLPAY_WEBHOOK_SECRET')
if not verify_signature(payload, signature, secret):
return 'Invalid signature', 401
event = request.get_json()
if event['event_type'] == 'order.paid':
handle_order_paid(event['data'])
elif event['event_type'] == 'order.refunded':
handle_order_refunded(event['data'])
return {'status': 'received'}, 200Retry Mechanism
If your endpoint doesn't respond with HTTP 2xx, Pixlpay retries automatically:
| Attempt | Delay |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours |
| 4th retry | 6 hours |
| 5th retry | 12 hours |
After 5 failed attempts, the delivery is marked as failed.
Best Practices
1. Respond Quickly
Return 2xx within 10 seconds. Process asynchronously:
// Good: Quick response, async processing
http_response_code(200);
Queue::push(new ProcessWebhookJob($event));2. Implement Idempotency
Webhooks may be delivered multiple times. Use the webhook ID to prevent duplicate processing:
function processWebhook($event) {
$webhookId = $event['id'];
if (WebhookLog::where('webhook_id', $webhookId)->exists()) {
return; // Already processed
}
// Process the event
handleEvent($event);
// Mark as processed
WebhookLog::create(['webhook_id' => $webhookId]);
}3. Use HTTPS
Production webhooks require HTTPS. For local development, use ngrok.
4. Validate Event Data
Don't trust data blindly - validate before processing:
function validateOrderPaid($data) {
$required = ['order_id', 'customer_email', 'total'];
foreach ($required as $field) {
if (empty($data[$field])) {
throw new Exception("Missing: $field");
}
}
}5. Log Everything
Log::info('Webhook received', [
'webhook_id' => $event['id'],
'event_type' => $event['event_type'],
]);6. Handle Errors Gracefully
try {
processWebhook($event);
return response()->json(['status' => 'success']);
} catch (TemporaryException $e) {
// Return 5xx to trigger retry
return response()->json(['error' => $e->getMessage()], 500);
} catch (PermanentException $e) {
// Return 2xx to prevent retries
Log::error('Permanent failure', ['error' => $e->getMessage()]);
return response()->json(['status' => 'failed']);
}Testing Webhooks
Using webhook.site
For quick testing without writing code:
- Go to webhook.site
- Copy your unique URL
- Create a webhook endpoint with that URL
- Trigger test events and watch them arrive
Using ngrok
For local development:
# Start your local server
php -S localhost:8000
# In another terminal
ngrok http 8000
# Use the ngrok URL for your webhook
# https://abc123.ngrok.io/webhooks/pixlpaySend Test Webhook
curl -X POST "https://yourstore.pixlpay.net/api/external/v1/webhooks/1/test" \
-H "Authorization: Bearer YOUR_API_TOKEN"Troubleshooting
Webhooks Not Received
- Check URL is accessible - Can you curl it from the internet?
- Check SSL certificate - Must be valid for production
- Check firewall rules - Allow Pixlpay's IPs
- Check webhook is active - Verify in dashboard
Signature Verification Fails
- Using correct secret - Check it matches the webhook
- Using raw body - Don't parse JSON before verifying
- Correct algorithm - Must be HMAC SHA-256
High Failure Rate
- Response too slow - Move processing to background queue
- Server errors - Check application logs
- Rate limiting - Ensure your server accepts Pixlpay requests