Merchant API authentication
July 30, 2026
Authenticate FlowAlp Pay Merchant API requests with the x-api-key header or the ApiSignature HMAC-SHA256 parameter, with code examples.
Every Merchant API request must prove that it comes from your backend. You need your instance name plus its API secret, and one of two authentication methods:
- `x-api-key` header (recommended): send the API secret with each request.
- `ApiSignature` parameter: send an HMAC-SHA256 signature computed from the request parameters — the secret itself is never transmitted.
If you build on PHP, the PHP SDK applies authentication for you; this page is mainly relevant for direct HTTP integrations.
Prerequisites
- Your instance name
- Your API credentials
- The API secret stored outside your code, for example in the
FLOWALP_PAY_API_SECRETenvironment variable
Option 1: x-api-key header (recommended)
Send the API secret in the x-api-key HTTP header. Header names are case-insensitive, so X-API-KEY is equivalent:
x-api-key: <api-secret>curl --request GET \
--url "https://api.pay.flowalp.com/v1.16/SignatureCheck/?instance=<instance>" \
--header "x-api-key: <api-secret>"<?php
$instance = getenv('FLOWALP_PAY_INSTANCE');
$apiSecret = getenv('FLOWALP_PAY_API_SECRET');
$ch = curl_init(
'https://api.pay.flowalp.com/v1.16/SignatureCheck/?instance=' . urlencode($instance)
);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key: ' . $apiSecret]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException('FlowAlp Pay auth check failed: ' . $status);
}const instanceName = process.env.FLOWALP_PAY_INSTANCE;
const apiSecret = process.env.FLOWALP_PAY_API_SECRET;
const response = await fetch(
`https://api.pay.flowalp.com/v1.16/SignatureCheck/?instance=${encodeURIComponent(instanceName)}`,
{ headers: { "x-api-key": apiSecret } }
);
if (!response.ok) {
throw new Error(`FlowAlp Pay auth check failed: ${response.status}`);
}Option 2: ApiSignature (HMAC-SHA256)
In signature mode you do not transmit the secret. Instead you sign the request parameters and send the result in the ApiSignature parameter. The signature is an HMAC (RFC 2104), built as follows:
- Collect all request parameters except
instance. - Build the URL-encoded query string from these parameters, for example
amount=2500¤cy=CHF. - Compute the binary HMAC-SHA256 of that string, using the API secret as the key.
- Base64-encode the result.
- Send the value as the
ApiSignatureparameter alongside the other parameters.
$apiSignature = base64_encode(
hash_hmac('sha256', http_build_query($params, '', '&'), $apiSecret, true)
);<?php
$instance = getenv('FLOWALP_PAY_INSTANCE');
$apiSecret = getenv('FLOWALP_PAY_API_SECRET');
$params = [
'amount' => 8925, // CHF 89.25 in minor units
'currency' => 'CHF',
'referenceId' => 'ORDER-975382',
];
// Sign exactly the query string you send as the body.
$body = http_build_query($params, '', '&');
$params['ApiSignature'] = base64_encode(
hash_hmac('sha256', $body, $apiSecret, true)
);
$ch = curl_init(
'https://api.pay.flowalp.com/v1.16/Gateway/?instance=' . urlencode($instance)
);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params, '', '&'));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);API_SECRET="<api-secret>"
QUERY_STRING="amount=2500¤cy=CHF"
SIGNATURE=$(printf '%s' "$QUERY_STRING" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary \
| openssl enc -base64)
curl --request POST \
--url "https://api.pay.flowalp.com/v1.16/Gateway/?instance=<instance>" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data "$QUERY_STRING" \
--data-urlencode "ApiSignature=$SIGNATURE"Query string encoding
The string you sign must be RFC 1738 form-encoded — spaces become +. The output of PHP's http_build_query() is the reference implementation. Other languages must match it exactly; pay attention to characters such as !'()*~, which some libraries encode differently (see the JavaScript helper below). The form-urlencoded request body itself uses percent-encoding, with spaces as %20.
Do not design your own variant of the algorithm. If your library produces a different encoding, the signature will not match. Validate your implementation with the SignatureCheck endpoint before going live.
const qs = require("qs");
const Base64 = require("crypto-js/enc-base64");
const hmacSHA256 = require("crypto-js/hmac-sha256");
function buildSignature(data, secret) {
let queryStr = "";
if (data) {
queryStr = qs.stringify(data, { format: "RFC1738" });
queryStr = queryStr.replace(
/[!'()*~]/g,
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
);
}
return Base64.stringify(hmacSHA256(queryStr, secret));
}
const params = { amount: 2500, currency: "CHF" };
const signed = {
...params,
ApiSignature: buildSignature(params, process.env.FLOWALP_PAY_API_SECRET),
};Which method should you use?
| Criterion | x-api-key | ApiSignature |
|---|---|---|
| Implementation effort | Minimal — one header | Requires exact query-string encoding |
| Secret in transit | Sent with every request (always over HTTPS) | Never transmitted; only the HMAC travels |
| Typical use | Server-to-server integrations | Environments with strict secret-handling policies |
Verify your setup
Call SignatureCheck after configuring either method; it validates your instance, your secret and — in signature mode — your encoding. Details: SignatureCheck: verify API credentials.
{
"status": "success",
"data": [
{
"id": 1
}
]
}Security recommendations
- Keep the API secret on your backend; never embed it in browsers, mobile apps or other client-side code.
- Store secrets in environment variables or a secret manager, not in the repository.
- Never log the
x-api-keyheader orApiSignaturevalues. - Rotate the secret immediately if you suspect it leaked.
- Always call the API over HTTPS.
Authenticated? Continue with the request format and send your first API request.