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.
<?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 processingUse 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:
| Aspect | Webhook signature | ApiSignature |
|---|---|---|
| What is signed | Raw POST body of the incoming webhook | URL-encoded, alphabetically sorted request parameters (all except instance) |
| Key | Webhook signing key | Your API secret |
| Encoding | Lowercase hexadecimal | Base64 |
| Where it travels | X-Webhook-Signature header, incoming | ApiSignature parameter, outgoing |
<?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:
https://api.pay.flowalp.com/v1.16/SignatureCheck/v1.14 · v1.15 · v1.16<?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.