Skip to content
PGSwift Docs
Documentation/Signature Verification

Signature Verification

When the gateway sends a webhook to your endpoint, it signs the request using its private key. You must verify the X-SIGNATURE header using the gateway's public key (provided during onboarding) to confirm the request is authentic.

How It Works

Webhook signature verification is the inverse of Signature Generation:

Step Generating (your API calls) Verifying (incoming webhooks)
Key used Your private key The gateway's public key
Action Sign the request Verify the signature
Purpose Prove the request is from you Confirm the request is from the gateway

Verification Algorithm

  1. Extract the three headers from the incoming request: X-SIGNATURE, X-TIMESTAMP, X-PARTNER-ID
  2. Read the raw request body
  3. Compute: SHA256(body) → lowercase hex string
  4. Build: stringToVerify = POST:{webhook_path}:{payload_hash}:{X-TIMESTAMP}
  5. Verify the signature using the gateway's public key with RSA-SHA256

Code Examples

<?php

function verifyWebhookSignature(
    string $webhookPath,
    string $rawBody,
    string $timestamp,
    string $receivedSignature,
    string $publicKeyPath
): bool {
    // Step 1: Hash the raw request body
    $payloadHash = strtolower(bin2hex(hash('sha256', $rawBody, true)));

    // Step 2: Build the string to verify
    $stringToVerify = "POST:{$webhookPath}:{$payloadHash}:{$timestamp}";

    // Step 3: Load the gateway's public key
    $publicKey = openssl_get_publickey('file://' . $publicKeyPath);
    if (!$publicKey) {
        throw new RuntimeException('Failed to load public key');
    }

    // Step 4: Verify the signature
    $result = openssl_verify(
        $stringToVerify,
        base64_decode($receivedSignature),
        $publicKey,
        OPENSSL_ALGO_SHA256
    );

    return $result === 1;
}

// Usage in your webhook handler:
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$partnerId = $_SERVER['HTTP_X_PARTNER_ID'] ?? '';

// Validate partner ID matches your client key
if ($partnerId !== 'your-client-key') {
    http_response_code(401);
    exit('Invalid partner ID');
}

// Verify signature
$isValid = verifyWebhookSignature(
    '/api/webhooks/payment',   // Your webhook endpoint path
    $rawBody,
    $timestamp,
    $signature,
    '/path/to/gateway-public-key.pem'
);

if (!$isValid) {
    http_response_code(401);
    exit('Invalid signature');
}

// Signature is valid — process the webhook
$payload = json_decode($rawBody, true);
$event = $payload['event'];
$data = $payload['data'];

// Handle the event...
http_response_code(200);
echo 'OK';

Security Checklist

  • Verify X-SIGNATURE on every incoming webhook using the gateway's public key
  • Validate X-PARTNER-ID matches your client key
  • Check X-TIMESTAMP freshness — reject requests older than 5 minutes to prevent replay attacks
  • Use HTTPS for your webhook endpoint
  • Read the raw body for signature verification before parsing JSON — middleware that parses/re-serializes the body can alter it and break verification
© 2026 PGSwift. All rights reserved.