Skip to content

Security & Fraud Prevention

Security & Fraud Prevention

SanPay is built with security at its core. From HMAC-signed webhooks to origin validation, we protect every transaction and integration point.

API Key Security

Your API keys are the gateway to your account. Follow these best practices:

Separate Keys by Environment

Use `pk_test_*` and `sk_test_*` for development, `pk_live_*` and `sk_live_*` for production.

Never Expose Secret Keys

Secret keys (sk_*) must stay on your server. Never include them in client-side code or mobile apps.

Rotate Keys Regularly

Generate new API keys periodically and revoke old ones from the dashboard.

Use Environment Variables

Store keys in environment variables, not in code repositories.

Key Types

Key TypePrefixUsage
Public Key pk_live_* / pk_test_* Client-side SDK initialization
Secret Key sk_live_* / sk_test_* Server-to-server API calls
Webhook Secret whsec_* Verify incoming webhook signatures

Webhook Signature Verification

Every webhook includes an HMAC-SHA256 signature to verify authenticity and prevent tampering.

Signature Format

The `X-Webhook-Signature` header contains:

t=1704556800000,v1=a1b2c3d4e5f6...

Verification Algorithm

  1. Extract timestamp (t) and signature (v1) from header
  2. Check timestamp is within acceptable window (5 minutes)
  3. Compute expected: HMAC-SHA256(secret, "{timestamp}.{body}")
  4. Use timing-safe comparison to verify signature
Signature Verification
// PHP Signature Verification
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
// Parse signature
preg_match('/^t=(\d+),v1=([a-f0-9]+)$/', $signature, $matches);
$timestamp = (int) $matches[1];
$receivedHash = $matches[2];
// Check timestamp (5 minute tolerance)
$now = (int) (microtime(true) * 1000);
if (abs($now - $timestamp) > 300000) {
throw new Exception('Signature expired');
}
// Verify signature
$signedPayload = "{$timestamp}.{$payload}";
$expectedHash = hash_hmac('sha256', $signedPayload, $webhookSecret);
if (!hash_equals($expectedHash, $receivedHash)) {
throw new Exception('Invalid signature');
}

Why Timing-Safe Comparison?

Standard string comparison can leak information through response timing. Attackers could deduce the signature character-by-character. Timing-safe comparison takes constant time regardless of match position.

Origin Validation

The SDK validates that requests come from authorized domains:

  • Configure allowed origins in your SanPay dashboard
  • Include protocol, domain, and port: https://example.com:443
  • Use wildcards carefully: https://*.example.com

Replay Attack Protection

Webhooks include timestamps to prevent replay attacks:

  • Reject signatures older than 5 minutes
  • Store processed eventIds to detect duplicates
  • All communication over HTTPS prevents interception

Rate Limiting

API endpoints are rate-limited to prevent abuse:

EndpointLimitWindow
Payment creation 100 req per minute
Status checks 300 req per minute
Asset listing 60 req per minute

Security Best Practices

Always Verify Webhooks

Never process a webhook without verifying its signature. Attackers could send fake payment confirmations.

Use Idempotency

Track eventIds to prevent processing the same event twice, especially for order fulfillment.

Validate Amounts

Always compare the received amount with your expected amount. Handle underpayments and overpayments explicitly.

HTTPS Only

We only deliver webhooks to HTTPS endpoints. Ensure your server has a valid SSL certificate.