Skip to content

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 status

If your server doesn't respond or returns an error, Pixlpay automatically retries.

Available Events

Order Events

EventDescription
order.createdNew order created (before payment)
order.paidPayment received successfully
order.completedOrder marked as fulfilled
order.refundedOrder was refunded

Subscription Events

EventDescription
subscription.createdNew subscription started
subscription.renewedSubscription renewed
subscription.cancelledSubscription cancelled

Product Events

EventDescription
product.createdNew product added
product.updatedProduct modified
product.deletedProduct removed

Creating a Webhook

Via API

bash
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

json
{
  "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:

json
{
  "id": "wh_delivery_uuid",
  "event_type": "order.paid",
  "created_at": "2025-01-20T14:30:00Z",
  "data": {
    // Event-specific payload
  }
}

Example: order.paid

json
{
  "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

HeaderDescription
X-Webhook-SignatureHMAC SHA-256 signature
X-Webhook-EventEvent type
X-Webhook-IDUnique delivery ID

Verification Process

  1. Get the raw request body (don't parse JSON first)
  2. Calculate HMAC SHA-256 using your secret
  3. Compare with the X-Webhook-Signature header

PHP Example

php
<?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

javascript
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

python
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'}, 200

Retry Mechanism

If your endpoint doesn't respond with HTTP 2xx, Pixlpay retries automatically:

AttemptDelay
1st retry5 minutes
2nd retry30 minutes
3rd retry2 hours
4th retry6 hours
5th retry12 hours

After 5 failed attempts, the delivery is marked as failed.

Best Practices

1. Respond Quickly

Return 2xx within 10 seconds. Process asynchronously:

php
// 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:

php
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:

php
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

php
Log::info('Webhook received', [
    'webhook_id' => $event['id'],
    'event_type' => $event['event_type'],
]);

6. Handle Errors Gracefully

php
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:

  1. Go to webhook.site
  2. Copy your unique URL
  3. Create a webhook endpoint with that URL
  4. Trigger test events and watch them arrive

Using ngrok

For local development:

bash
# 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/pixlpay

Send Test Webhook

bash
curl -X POST "https://yourstore.pixlpay.net/api/external/v1/webhooks/1/test" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Troubleshooting

Webhooks Not Received

  1. Check URL is accessible - Can you curl it from the internet?
  2. Check SSL certificate - Must be valid for production
  3. Check firewall rules - Allow Pixlpay's IPs
  4. Check webhook is active - Verify in dashboard

Signature Verification Fails

  1. Using correct secret - Check it matches the webhook
  2. Using raw body - Don't parse JSON before verifying
  3. Correct algorithm - Must be HMAC SHA-256

High Failure Rate

  1. Response too slow - Move processing to background queue
  2. Server errors - Check application logs
  3. Rate limiting - Ensure your server accepts Pixlpay requests

Built for game developers, by game developers.