Skip to main content

Webhook Implementation

Technical guide for implementing Paysera webhooks with security best practices and code examples.

Request Structure​

Each webhook POST request contains two parameters:

ParameterDescription
dataBase64-encoded event information
signBase64-encoded RSA signature of the data

Processing Webhooks​

Step 1: Decode Parameters​

Both data and sign are encoded using a modified Base64 scheme:

  1. Replace characters: - → + and _ → /
  2. Decode using standard Base64

Step 2: Verify Signature​

Step 3: Extract Event Data​

  • Decode the verified data parameter
  • Parse URL-encoded string to extract event parameters

Step 4: Send Response​

  • Return HTTP response starting with or equal to OK
  • Response confirms successful receipt

Implementation Examples​

<?php

$publicKey = file_get_contents('https://www.paysera.com/download/public.key');
$sign = $_POST['sign'];
$data = $_POST['data'];

// Decode signature
$signReplaced = strtr($sign, array('-' => '+', '_' => '/'));
$signDecoded = base64_decode($signReplaced);

// Verify signature
if (openssl_verify($data, $signDecoded, $publicKey, OPENSSL_ALGO_SHA1) === 1) {
// Decode data
$dataReplaced = strtr($data, array('-' => '+', '_' => '/'));
$dataDecoded = base64_decode($dataReplaced);
parse_str($dataDecoded, $params);

// Process event parameters
// $params contains event data (type, amount, account, etc.)

// Confirm receipt
echo 'OK';
} else {
// Signature verification failed
http_response_code(400);
}

Security Best Practices​

Essential Security Measures​

PracticeDescription
Verify SignaturesAlways validate RSA signature before processing
Use HTTPSOnly accept webhooks over secure HTTPS connections
Check Statement IDImplement idempotency using statement_id
Validate DataVerify all parameters meet expected formats
Rate LimitingImplement rate limiting on your endpoint
Error HandlingLog failures but don't expose sensitive data

Idempotency Implementation​

Always check the statement_id to prevent duplicate processing:

// Check if this statement was already processed
if (!isStatementProcessed($params['statement_id'])) {
// Process the transaction
processPayment($params);

// Mark statement as processed
markStatementProcessed($params['statement_id']);
}

echo 'OK';

Public Key Caching​

Cache the public key to improve performance:

$publicKey = getFromCache('paysera_public_key');

if (!$publicKey) {
$publicKey = file_get_contents('https://www.paysera.com/download/public.key');
saveToCache('paysera_public_key', $publicKey, 86400); // Cache for 24h
}

Response Requirements​

Success Response​

Your endpoint must return a response that:

  • Starts with or equals OK
  • Returns within reasonable timeout (recommended: < 5 seconds)

Valid Responses:

OK
OK - Processed
OK: Statement 123456789 received

Error Responses​

For invalid requests:

  • Return HTTP 400 Bad Request
  • Do not return 'OK'
  • Log the error for investigation
if (!$signatureValid) {
http_response_code(400);
error_log("Invalid signature for statement: " . $_POST['data']);
exit;
}

Troubleshooting​

Common Issues​

IssuePossible CauseSolution
Webhook not receivedFirewall blocking requestsCheck firewall settings, whitelist Paysera IPs
Signature verification failsIncorrect public key or decodingDownload latest public key, verify decoding logic
Duplicate notificationsNo idempotency checkImplement statement_id checking
Invalid data formatIncorrect Base64 decodingReplace - with + and _ with / before decoding
Timeout errorsSlow response from endpointOptimize processing, respond with 'OK' immediately

Debug Mode​

Enable detailed logging during development:

error_log("Received webhook data: " . print_r($params, true));
error_log("Statement ID: " . $params['statement_id']);
error_log("Amount: " . $params['amount'] . " " . $params['currency']);

Example Use Cases​

Payment Confirmation​

Automatically update order status when payment is received:

if ($params['type'] === 'MK' && $params['credit'] === '1') {
// Incoming payment received
$orderId = extractOrderId($params['details']);

// Update order status
updateOrderStatus($orderId, 'paid');

// Send confirmation email
sendConfirmationEmail($orderId);

// Update inventory
releaseReservedStock($orderId);
}

Full Example:

function processEcommercePayment($params) {
// Extract order ID from payment details
preg_match('/Order #(\d+)/', $params['details'], $matches);
$orderId = $matches[1] ?? null;

if (!$orderId) {
error_log("No order ID found in payment details");
return;
}

// Update order
$order = getOrder($orderId);
$order->status = 'paid';
$order->payment_date = date('Y-m-d H:i:s', $params['created_at']);
$order->transaction_id = $params['transfer_id'];
$order->save();

// Send emails
sendCustomerConfirmation($orderId);
sendAdminNotification($orderId);

// Trigger fulfillment
triggerFulfillment($orderId);
}

Next Steps​

Support​