FlowAlp

Verify webhook signatures

July 30, 2026

Verify FlowAlp Pay webhook authenticity: HMAC-SHA256 signature checks in PHP, the ApiSignature pattern and credential tests via SignatureCheck.

A webhook endpoint is a door into your order system, so every incoming event must prove that it really comes from FlowAlp Pay. Two mechanisms work together: the signature header on the webhook itself, and server-side validation against the Merchant API.

How the webhook signature works

Each webhook carries the X-Webhook-Signature HTTP header. Its value is an HMAC-SHA256 computed over the raw request body with your signing key, encoded as lowercase hexadecimal. Three details matter:

  • Signed data: the raw, unmodified request body — never re-serialize the parsed JSON before hashing.
  • Key encoding: the signing key is used as a plain UTF-8 string; it is not Base64-decoded first.
  • Signature encoding: the result is lowercase hex — not Base64.

The signing key belongs to your webhook configuration in the dashboard at https://pay.flowalp.com. If your webhook settings do not show a signing key for your account, do not guess header names or keys: rely on the API-side validation below and confirm the signature feature for your account with FlowAlp support before going live.

Verify and reject in PHPPHP
<?php
// Reject any webhook whose signature does not match
$rawBody    = file_get_contents('php://input');
$received   = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$signingKey = getenv('FLOWALP_WEBHOOK_SIGNING_KEY');

// Lowercase hex HMAC-SHA256 of the raw body; the key is a plain UTF-8 string
$expected = hash_hmac('sha256', $rawBody, $signingKey);

if (!hash_equals($expected, $received)) {
    http_response_code(401); // non-2xx: the delivery counts as failed
    exit('invalid signature');
}

http_response_code(200);
// Queue the event for asynchronous, idempotent processing

Use hash_equals() (or your language's constant-time comparison) to avoid timing attacks, and answer unsigned or invalid deliveries with a non-2xx status such as 401 — never process them.

Do not confuse it with ApiSignature

The Merchant API knows a second, unrelated HMAC: the ApiSignature authentication mode for outgoing API requests. Keep the two apart:

AspectWebhook signatureApiSignature
What is signedRaw POST body of the incoming webhookURL-encoded, alphabetically sorted request parameters (all except instance)
KeyWebhook signing keyYour API secret
EncodingLowercase hexadecimalBase64
Where it travelsX-Webhook-Signature header, incomingApiSignature parameter, outgoing
ApiSignature helperPHP
<?php
function flowalpApiSignature(array $params, string $apiSecret): string
{
    ksort($params);
    $query = http_build_query($params, '', '&');
    return base64_encode(hash_hmac('sha256', $query, $apiSecret, true));
}

The exact parameter-encoding rules for ApiSignature are documented in Merchant API authentication.

Validate credentials with SignatureCheck

The SignatureCheck resource verifies your instance name, API secret and — in signature mode — your HMAC implementation, without creating anything. Call it during setup and from health checks:

GEThttps://api.pay.flowalp.com/v1.16/SignatureCheck/v1.14 · v1.15 · v1.16
SignatureCheck with the PHP SDKPHP
<?php
use FlowAlpPay\FlowAlpPay;
use FlowAlpPay\Models\Request\SignatureCheck;

$client = new FlowAlpPay(
    getenv('FLOWALP_TENANT'),
    getenv('FLOWALP_API_SECRET'),
    FlowAlpPay::DEFAULT_COMMUNICATION_HANDLER,
    'pay.flowalp.com',
    '1.16'
);

try {
    $client->getOne(new SignatureCheck());
    // Credentials are valid
} catch (\FlowAlpPay\FlowAlpPayException $e) {
    // Wrong instance name or API secret
}

Details and error responses are on the SignatureCheck reference page; the client setup is covered in the PHP SDK guide.

Defense in depth

  • Reject unsigned or invalid webhooks with a non-2xx response and alert on repeated failures.
  • Process verified events idempotently — deduplicate on transaction ID plus status.
  • For money-relevant state changes, additionally retrieve the transaction through the API before fulfilling.
  • Persist the verification outcome next to the raw payload for audits.

Next step: revisit the webhook payloads your handler must support, then walk the go-live checklist.