Skip to content

PHP Examples

Ready-to-use PHP code for common Pixlpay API operations.

Setup

Using cURL

php
<?php

class PixlpayClient
{
    private string $baseUrl;
    private string $apiToken;

    public function __construct(string $subdomain, string $apiToken)
    {
        $this->baseUrl = "https://{$subdomain}.pixlpay.net/api/external/v1";
        $this->apiToken = $apiToken;
    }

    public function request(string $method, string $endpoint, array $data = []): array
    {
        $ch = curl_init();
        $url = $this->baseUrl . $endpoint;

        $headers = [
            'Authorization: Bearer ' . $this->apiToken,
            'Accept: application/json',
            'Content-Type: application/json',
        ];

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [
            'status' => $httpCode,
            'data' => json_decode($response, true),
        ];
    }

    public function get(string $endpoint): array
    {
        return $this->request('GET', $endpoint);
    }

    public function post(string $endpoint, array $data): array
    {
        return $this->request('POST', $endpoint, $data);
    }
}

Fetching Products

php
<?php

$client = new PixlpayClient('yourstore', 'your_api_token');

// Get all products
$response = $client->get('/products');

if ($response['status'] === 200) {
    foreach ($response['data']['data'] as $product) {
        echo "{$product['name']} - \${$product['price']}\n";
    }
}

// Get single product
$response = $client->get('/products/1');
$product = $response['data']['data'];

Fetching Orders

php
<?php

$client = new PixlpayClient('yourstore', 'your_api_token');

// Get pending orders
$response = $client->get('/orders?status=pending');

foreach ($response['data']['data'] as $order) {
    echo "Order #{$order['order_number']} - {$order['customer']['email']}\n";
}

// Get orders from date range
$response = $client->get('/orders?from=2025-01-01&to=2025-01-31');

Fulfilling Orders

php
<?php

$client = new PixlpayClient('yourstore', 'your_api_token');

// Mark order as fulfilled
$orderId = 1;
$response = $client->post("/orders/{$orderId}/fulfill", [
    'note' => 'Delivered via API',
]);

if ($response['status'] === 200) {
    echo "Order fulfilled successfully!\n";
}

Webhook Signature Verification

Pixlpay signs all webhooks with HMAC-SHA256. Always verify signatures before processing webhook payloads.

Signature Verification Function

php
<?php

/**
 * Verify webhook signature using HMAC-SHA256.
 *
 * @param string $payload Raw request body (not parsed JSON)
 * @param string $signature Value from X-Webhook-Signature header
 * @param string $secret Your webhook secret
 * @return bool True if signature is valid
 */
function verifyWebhookSignature(string $payload, string $signature, string $secret): bool
{
    // Compute HMAC-SHA256 signature
    $computed = hash_hmac('sha256', $payload, $secret);

    // Use timing-safe comparison to prevent timing attacks
    return hash_equals($computed, $signature);
}

Complete Webhook Handler

php
<?php

// webhook-handler.php

// Get raw request body BEFORE any parsing
$payload = file_get_contents('php://input');

// Get headers (PHP converts X-Webhook-Signature to HTTP_X_WEBHOOK_SIGNATURE)
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$eventType = $_SERVER['HTTP_X_WEBHOOK_EVENT'] ?? '';
$deliveryId = $_SERVER['HTTP_X_WEBHOOK_ID'] ?? '';

// Get secret from environment
$secret = getenv('PIXLPAY_WEBHOOK_SECRET');

// Verify signature FIRST
$computed = hash_hmac('sha256', $payload, $secret);
if (!hash_equals($computed, $signature)) {
    // Log the failure for security monitoring
    error_log(json_encode([
        'error' => 'webhook_signature_invalid',
        'delivery_id' => $deliveryId,
        'ip' => $_SERVER['REMOTE_ADDR'],
        'timestamp' => date('c'),
    ]));

    http_response_code(401);
    exit('Invalid signature');
}

// Now safe to parse the payload
$event = json_decode($payload, true);
$data = $event['data'];

// Handle different events
switch ($eventType) {
    case 'order.received':
        handleOrderReceived($data);
        break;
    case 'order.refunded':
        handleOrderRefunded($data);
        break;
    case 'subscription.created':
        handleSubscriptionCreated($data);
        break;
    case 'subscription.cancelled':
        handleSubscriptionCancelled($data);
        break;
    case 'delivery.completed':
        handleDeliveryCompleted($data);
        break;
    case 'delivery.failed':
        handleDeliveryFailed($data);
        break;
    default:
        // Unknown event type - log but don't fail
        error_log("Unknown webhook event: {$eventType}");
}

// Respond quickly with 200
http_response_code(200);
echo 'OK';

// Event handlers
function handleOrderReceived(array $order): void
{
    error_log("Order received: {$order['order_number']}");
    // Your delivery logic here
}

function handleOrderRefunded(array $order): void
{
    error_log("Order refunded: {$order['order_number']}");
    // Revoke access, remove roles, etc.
}

function handleSubscriptionCreated(array $subscription): void
{
    error_log("Subscription created: {$subscription['id']}");
    // Grant recurring access
}

function handleSubscriptionCancelled(array $subscription): void
{
    error_log("Subscription cancelled: {$subscription['id']}");
    // Revoke recurring access
}

function handleDeliveryCompleted(array $delivery): void
{
    error_log("Delivery completed: {$delivery['id']}");
}

function handleDeliveryFailed(array $delivery): void
{
    error_log("Delivery failed: {$delivery['id']}");
    // Alert admin, retry logic, etc.
}

Laravel Webhook Handler

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;

class PixlpayWebhookController extends Controller
{
    public function handle(Request $request): Response
    {
        // Get raw content for signature verification
        $payload = $request->getContent();

        // Get headers
        $signature = $request->header('X-Webhook-Signature', '');
        $eventType = $request->header('X-Webhook-Event', '');
        $deliveryId = $request->header('X-Webhook-ID', '');

        // Get secret from config
        $secret = config('services.pixlpay.webhook_secret');

        // Verify signature
        $computed = hash_hmac('sha256', $payload, $secret);
        if (!hash_equals($computed, $signature)) {
            Log::warning('Invalid Pixlpay webhook signature', [
                'delivery_id' => $deliveryId,
                'ip' => $request->ip(),
            ]);

            return response('Invalid signature', 401);
        }

        // Parse and process
        $event = json_decode($payload, true);
        $data = $event['data'] ?? [];

        // Dispatch to appropriate handler
        match ($eventType) {
            'order.received' => $this->handleOrderReceived($data),
            'order.refunded' => $this->handleOrderRefunded($data),
            'subscription.created' => $this->handleSubscriptionCreated($data),
            'subscription.cancelled' => $this->handleSubscriptionCancelled($data),
            default => Log::info("Unhandled webhook event: {$eventType}"),
        };

        return response('OK', 200);
    }

    private function handleOrderReceived(array $order): void
    {
        Log::info("Order received: {$order['order_number']}");
        // Queue job for processing
        // ProcessOrderJob::dispatch($order);
    }

    private function handleOrderRefunded(array $order): void
    {
        Log::info("Order refunded: {$order['order_number']}");
    }

    private function handleSubscriptionCreated(array $subscription): void
    {
        Log::info("Subscription created: {$subscription['id']}");
    }

    private function handleSubscriptionCancelled(array $subscription): void
    {
        Log::info("Subscription cancelled: {$subscription['id']}");
    }
}

Webhook Verification with Idempotency

Prevent replay attacks by tracking processed webhooks:

php
<?php

use Illuminate\Support\Facades\Cache;

class PixlpayWebhookController extends Controller
{
    public function handle(Request $request): Response
    {
        $payload = $request->getContent();
        $signature = $request->header('X-Webhook-Signature', '');
        $deliveryId = $request->header('X-Webhook-ID', '');
        $secret = config('services.pixlpay.webhook_secret');

        // Verify signature
        $computed = hash_hmac('sha256', $payload, $secret);
        if (!hash_equals($computed, $signature)) {
            return response('Invalid signature', 401);
        }

        // Check for replay attack (idempotency)
        $cacheKey = "pixlpay_webhook:{$deliveryId}";
        if (Cache::has($cacheKey)) {
            // Already processed - return success but don't reprocess
            return response('Already processed', 200);
        }

        // Process the webhook...
        $event = json_decode($payload, true);
        $this->processEvent($event);

        // Mark as processed (cache for 24 hours)
        Cache::put($cacheKey, true, now()->addDay());

        return response('OK', 200);
    }
}

Creating Webhooks

php
<?php

$client = new PixlpayClient('yourstore', 'your_api_token');

$response = $client->post('/webhooks', [
    'url' => 'https://yoursite.com/webhooks/pixlpay',
    'events' => ['order.received', 'order.refunded', 'subscription.cancelled'],
]);

if ($response['status'] === 201) {
    $webhook = $response['data']['data'];
    echo "Webhook created! Secret: {$webhook['secret']}\n";

    // IMPORTANT: Store this secret securely - you'll need it for verification
    // Save to .env or secure config
}

Error Handling

php
<?php

$client = new PixlpayClient('yourstore', 'your_api_token');
$response = $client->get('/products/99999');

if ($response['status'] !== 200) {
    $error = $response['data'];
    echo "Error: {$error['message']}\n";

    switch ($response['status']) {
        case 401:
            echo "Check your API token\n";
            break;
        case 404:
            echo "Resource not found\n";
            break;
        case 429:
            echo "Rate limited - slow down!\n";
            break;
    }
}

Built for game developers, by game developers.