Skip to content

Webhook Verification

Always verify webhook signatures to ensure requests are from Pixlpay.

Why Verify?

Without verification, attackers could send fake webhook payloads to your endpoint. Signature verification ensures the request actually came from Pixlpay.

How Verification Works

  1. Pixlpay signs each webhook with your secret using HMAC-SHA256
  2. The signature is sent in the X-Pixlpay-Signature header
  3. You compute the signature using the raw request body
  4. Compare your computed signature with the header value

Verification Code Examples

PHP

php
function verifyWebhook(string $payload, string $signature, string $secret): bool
{
    $computed = hash_hmac('sha256', $payload, $secret);
    return hash_equals($computed, $signature);
}

// Usage
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PIXLPAY_SIGNATURE'] ?? '';
$secret = getenv('PIXLPAY_WEBHOOK_SECRET');

if (!verifyWebhook($payload, $signature, $secret)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($payload, true);
// Process the event...

Node.js

javascript
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
    const computed = crypto
        .createHmac('sha256', secret)
        .update(payload)
        .digest('hex');
    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(signature)
    );
}

// Express.js example
app.post('/webhooks/pixlpay', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-pixlpay-signature'];
    const secret = process.env.PIXLPAY_WEBHOOK_SECRET;

    if (!verifyWebhook(req.body, signature, secret)) {
        return res.status(401).send('Invalid signature');
    }

    const event = JSON.parse(req.body);
    // Process the event...

    res.sendStatus(200);
});

Python

python
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(computed, signature)

# Flask example
from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/webhooks/pixlpay', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Pixlpay-Signature', '')
    secret = os.environ['PIXLPAY_WEBHOOK_SECRET']

    if not verify_webhook(request.data, signature, secret):
        abort(401, 'Invalid signature')

    event = request.json
    # Process the event...

    return '', 200

Go

go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
    "os"
)

func verifyWebhook(payload []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    computed := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(computed), []byte(signature))
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    payload, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Pixlpay-Signature")
    secret := os.Getenv("PIXLPAY_WEBHOOK_SECRET")

    if !verifyWebhook(payload, signature, secret) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    // Process the event...
    w.WriteHeader(http.StatusOK)
}

Best Practices

  1. Always verify - Never skip signature verification
  2. Use timing-safe comparison - Prevents timing attacks
  3. Use raw body - Parse JSON after verification
  4. Secure your secret - Use environment variables
  5. Log failures - Track invalid signature attempts

Troubleshooting

Signature Mismatch

  • Ensure you're using the raw request body (not parsed JSON)
  • Check your secret is correct
  • Verify no middleware modified the body
  • Check for encoding issues

Missing Header

  • Ensure your framework passes through all headers
  • Header name is X-Pixlpay-Signature (case-insensitive)

Testing

Use the webhook test feature to verify your implementation:

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

Built for game developers, by game developers.