Skip to main content

Request Signature (X-Signature)

Mandatory

Request signature is mandatory for the Money Transfer and International Transfer APIs listed in APIs That Require a Signature. Complete the setup on this page before you go live.

A request signature proves that a request came from you and that its parameters were not changed on the way to Flip. You sign every request with your private key. Flip checks the signature with the public key you registered. If the signature does not match the parameters Flip receives, Flip rejects the request with HTTP 403 Forbidden.

How It Works​

  1. You generate an RSA 2048-bit key pair: a private key and a public key.
  2. You send the public key to Flip by email. Flip registers it to your account and enables the signature feature.
  3. For every request, you build a signature payload from the request parameters, sign it with your private key (sha256WithRSAEncryption), and Base64-encode the result.
  4. You send the Base64 string in the X-Signature header.
  5. Flip rebuilds the same payload from the parameters it receives and verifies your signature with your public key.

Signature is verified in both Sandbox and Production once the public key for that environment is registered.

APIs That Require a Signature​

Once signature is enabled for your account, include X-Signature on every request to these APIs, including GET requests.

Money Transfer​

APIMethodEndpoint
Bank Account InquiryPOST/v2/disbursement/bank-account-inquiry
City ListGET/v2/disbursement/city-list
Country ListGET/v2/disbursement/country-list
City and Country ListGET/v2/disbursement/city-country-list
Create DisbursementPOST/v3/disbursement
Get All DisbursementGET/v3/disbursement
Get DisbursementGET/v3/get-disbursement
Create Special Money TransferPOST/v3/special-disbursement

International Transfer​

APIMethodEndpoint
Get Exchange RatesGET/v2/international-disbursement/exchange-rates
Get Form DataGET/v2/international-disbursement/form-data
Get International TransferGET/v2/international-disbursement/{transaction_id}
Get All International TransferGET/v2/international-disbursement
Create C2C / C2B International TransferPOST/v2/international-disbursement
Create B2C / B2B International TransferPOST/v2/international-disbursement/create-with-attachment
tip

The two API families encode the signature payload slightly differently. See Encoding Differences Between API Families in Step 3, and pass the right mode to the code samples.

Step 1: Generate Your Private and Public Key​

No OpenSSL? Use the simulator

For Sandbox testing you can generate a test key pair in the Signature Simulator and download both .pem files without a terminal. For Production, generate the key pair on a machine your team controls, as described below.

Generate the key pair with OpenSSL. OpenSSL is included with macOS and most Linux distributions. On Windows, you can use the OpenSSL that comes with Git for Windows (Git Bash).

Generate keys on a secure machine that your team controls. Do not use online key generators.

1. Generate the private key (RSA 2048-bit)

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private_key.pem

2. Extract the public key from the private key

openssl pkey -in private_key.pem -pubout -out public_key.pem

3. Check the result

openssl pkey -in private_key.pem -noout -text | head -1
# Private-Key: (2048 bit, 2 primes)

cat public_key.pem

You now have two files:

FileStarts withWhat to do with it
private_key.pem-----BEGIN PRIVATE KEY-----Keep it secret. Store it in your server's secret manager and use it to sign requests. Never share it with anyone, including Flip.
public_key.pem-----BEGIN PUBLIC KEY-----Send it to Flip (see Step 2). It is safe to share.

The public key looks like this:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjhj9bAWzKsLAMlo6+Bmu
...
-----END PUBLIC KEY-----
Protect your private key

Anyone with your private key and your API Secret Key can send valid requests as you. Don't commit it to source control, log it, or send it by email or chat. If you think it has leaked, generate a new key pair and send the new public key to Flip right away.

Step 2: Share Your Public Key With Flip​

Send your public key by email to Flip so we can register it and enable the signature feature on your account.

Use this email template:

Subject: Request setup Public Key and Enable signature feature

Hi Flip Team,

We would like to set up our public key and enable the signature feature for our account.

Flip for Business Email : <email account registered on Flip for Business>
Company Name : <your registered company name>
Environment : <Sandbox / Production>
PIC Name : <technical contact name>
PIC Email/Phone : <technical contact email or phone>

Our public key is attached (public_key.pem), and pasted below:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

Thank you.
tip
  • Send only the public key. Never attach private_key.pem.
  • Sandbox and Production are set up separately. Name the environment in your email, and send a separate request for each one.
  • Flip will reply to confirm when the public key is registered and the signature feature is on. From then on, requests without a valid X-Signature are rejected on all the APIs listed above, so update your code (Step 3) for every API you use before you ask us to enable it.

Step 3: Build the Signature Payload​

Flip does not sign the raw HTTP body. It signs a JSON string built from the request parameters. Your payload must match Flip's byte for byte, or the signature check fails.

Build the payload like this:

  1. Collect the parameters, in this order:

    1. Query string parameters, in the order they appear in the URL
    2. Body parameters (application/x-www-form-urlencoded or multipart/form-data fields), in the order they appear in the body

    Headers (such as idempotency-key and X-TIMESTAMP) are not part of the payload. File fields (such as attachment_data on Create B2C / B2B International Transfer) are not part of the payload either; sign only the text fields.

  2. Use each parameter name only once. Don't send the same parameter twice.

  3. Keep the parameters in the order you send them. Do not sort them.

  4. Treat every value as a string, exactly as it appears in the request, for example "10000", not 10000.

  5. Encode as compact JSON: {"name":"value","name":"value"}, with no spaces or line breaks between elements. Some characters are escaped differently per API family; see the next section.

  6. If the request has no parameters, the payload is {} for Money Transfer APIs and [] for International Transfer APIs.

Encoding Differences Between API Families​

Most payloads contain only letters, digits, and common punctuation, and then both families produce the same string. The differences only show up in these cases:

CaseMoney Transfer APIsInternational Transfer APIs
/ in a value, e.g. Jl. Sudirman 1/2Not escaped: "Jl. Sudirman 1/2"Escaped: "Jl. Sudirman 1\/2"
Non-ASCII characters, e.g. é, 中, emojiKept as UTF-8: "José"Escaped as \uXXXX (lowercase hex, UTF-16 pairs for emoji): "José"
&, <, > in a valueEscaped as \u0026, \u003c, \u003e: "Tom \u0026 Jerry"Not escaped: "Tom & Jerry"
Request with no parameters{}[]
Path parameterNot applicableGet International Transfer signs the transaction ID from the URL as id: {"id":"<transaction_id>"}

The code samples below handle both families: pass the International Transfer mode when you call an International Transfer API.

Payload Examples​

Request:

POST /v3/disbursement
idempotency-key: your-unique-id-12345 # header, not signed

account_number=5465327020&bank_code=bca&amount=10000&remark=testing&recipient_city=391&beneficiary_email=

Signature payload:

{"account_number":"5465327020","bank_code":"bca","amount":"10000","remark":"testing","recipient_city":"391","beneficiary_email":""}

Step 4: Sign the Payload and Send the Request​

Sign the payload string (UTF-8 bytes) with your private key using RSA PKCS#1 v1.5 with SHA-256 (sha256WithRSAEncryption). Base64-encode the result and send it in the X-Signature header.

Code Samples​

Each sample has a function that builds the payload with the rules above (with a switch for the International Transfer encoding) and a function that signs it.

<?php

/**
* Build the signature payload: query params first, then body params.
* Each name is used once (the first occurrence wins) and every value is a string.
* Set $international to true for the International Transfer APIs.
*/
function buildSignaturePayload(array $queryParams = [], array $bodyParams = [], bool $international = false): string
{
// The "+" operator keeps the first occurrence of a name
$mergedParams = array_map('strval', $queryParams + $bodyParams);

if ($international) {
// International Transfer: default json_encode() output ("/" and non-ASCII are escaped, "[]" when empty)
return json_encode($mergedParams);
}

if (empty($mergedParams)) {
return '{}';
}

// Money Transfer: do not escape "/" or non-ASCII characters; escape &, < and > the way Flip does
$payload = json_encode($mergedParams, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

// Single quotes keep the backslash, so the output contains the literal text \u0026 etc.
return str_replace(['&', '<', '>'], ['\u0026', '\u003c', '\u003e'], $payload);
}

/**
* Sign the payload with RSA SHA-256 and return the Base64-encoded signature.
*/
function generateSignature(string $privateKeyPem, string $payload): string
{
$privateKey = openssl_pkey_get_private($privateKeyPem);
openssl_sign($payload, $rawSignature, $privateKey, OPENSSL_ALGO_SHA256);

return base64_encode($rawSignature);
}

// Example: Create Disbursement (Money Transfer API)
$bodyParams = [
'account_number' => '5465327020',
'bank_code' => 'bca',
'amount' => '10000',
'remark' => 'testing',
'recipient_city' => '391',
'beneficiary_email' => '',
];

$payload = buildSignaturePayload([], $bodyParams);
$signature = generateSignature(file_get_contents('/secure/path/private_key.pem'), $payload);

$ch = curl_init('https://bigflip.id/big_sandbox_api/v3/disbursement');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($bodyParams), // send the same params, in the same order
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Basic ' . base64_encode('YOUR_API_SECRET_KEY:'),
'idempotency-key: your-unique-id-12345',
'X-Signature: ' . $signature,
],
]);
$response = curl_exec($ch);

// Example: Get Exchange Rates (International Transfer API)
$queryParams = ['country_iso_code' => 'SGP', 'transaction_type' => 'C2C'];
$payload = buildSignaturePayload($queryParams, [], true);
// GET https://bigflip.id/big_sandbox_api/v2/international-disbursement/exchange-rates?country_iso_code=SGP&transaction_type=C2C
// with header "X-Signature: " . generateSignature($privateKeyPem, $payload)

Sample Requests With Signature​

curl -X POST 'https://bigflip.id/big_sandbox_api/v3/disbursement' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json; charset=UTF-8' \
-H 'Authorization: Basic <Base64(Your-API-SecretKey + :)>' \
-H 'idempotency-key: your-unique-id-12345' \
-H 'X-Signature: <Your-Generated-Signature>' \
-d 'account_number=5465327020' \
-d 'bank_code=bca' \
-d 'amount=10000' \
-d 'remark=testing' \
-d 'recipient_city=391' \
-d 'beneficiary_email='

The signature payload for this request is:

{"account_number":"5465327020","bank_code":"bca","amount":"10000","remark":"testing","recipient_city":"391","beneficiary_email":""}

Optional: X-TIMESTAMP Header​

X-TIMESTAMP is not part of the signature, and you don't need it to use signatures. You can send it on the Create APIs that list it in the API reference so that Flip doesn't process a request that was delayed: Flip rejects the request if the timestamp is more than 60 seconds old. If you leave the header out, this check is skipped.

  • Format: ISO 8601 with a timezone offset, for example 2026-10-06T10:15:30+07:00
  • Clock: keep your server clock in sync (for example with NTP), or valid requests may be rejected as stale
  • Error: a stale request fails with the message Request sent at <timestamp>, and marked as stale
-H 'X-TIMESTAMP: 2026-10-06T10:15:30+07:00'

Test Your Signature​

Signature Simulator​

The quickest check is the Signature Simulator. It walks you through the whole flow in the browser:

  1. Keys: generate a test key pair, or paste your own private key.
  2. Request: pick the API and enter the parameters in the order you send them. The simulator shows the exact payload string.
  3. Sign: get the X-Signature for that payload and a ready-to-send cURL command.
  4. Verify: paste your public key and the signature your own code produced. The simulator tells you whether Flip would accept it, and if not, which common mistake your payload matches.

Everything runs in your browser. Nothing you enter is sent to Flip or anywhere else. Use a test key for signing in the simulator; keep your Production private key on your servers.

OpenSSL​

You can also verify with OpenSSL. Use your code to print the payload and the signature, then run:

# 1. Save the exact payload your code generated (printf adds no trailing newline)
printf '%s' '{"account_number":"5465327020","bank_code":"bca","amount":"10000","remark":"testing","recipient_city":"391","beneficiary_email":""}' > payload.txt

# 2. Save the Base64 signature your code generated, then decode it
echo '<Your-Generated-Signature>' | openssl base64 -d -A > signature.bin

# 3. Verify it with your public key
openssl dgst -sha256 -verify public_key.pem -signature signature.bin payload.txt
# Verified OK

You can also make a reference signature with OpenSSL alone and compare it with your code's output. RSA PKCS#1 v1.5 signatures are deterministic, so the same key and payload always give the same signature:

printf '%s' '{"account_number":"5465327020","bank_code":"bca","amount":"10000","remark":"testing","recipient_city":"391","beneficiary_email":""}' \
| openssl dgst -sha256 -sign private_key.pem | openssl base64 -A

Troubleshooting​

If Flip returns HTTP 403 Forbidden with the message You are not allowed to perform this action. after signature is enabled, check these common causes:

CauseFix
Missing X-Signature headerSend the header on every request to the APIs listed above, including GET requests and requests without parameters.
Parameters sorted or reorderedBuild the payload in the exact order you send the parameters.
Payload has spaces, e.g. {"a": "b"}Use compact JSON with no spaces: {"a":"b"}.
Wrong rules for the API familyMoney Transfer and International Transfer APIs escape /, non-ASCII and &<> differently. See Encoding Differences and use the matching mode in your code.
/ escaped as \/ on a Money Transfer APIDon't escape /. In PHP, pass JSON_UNESCAPED_SLASHES to json_encode().
/ not escaped on an International Transfer APIEscape / as \/ (PHP's default json_encode() does this).
Numbers signed as JSON numbersEvery value must be a string: "amount":"10000".
Value in payload differs from value sentSign exactly the value you send. Don't trim, reformat, or round it after signing.
Headers included in the payloadDon't sign headers such as idempotency-key or X-TIMESTAMP. Sign only query and body parameters.
File field included in the payloadDon't sign file uploads such as attachment_data. Sign only the text fields.
Get International Transfer signed with an empty payloadThe transaction ID in the URL is signed: {"id":"<transaction_id>"}.
Same parameter sent twiceSend each parameter name once.
Request without parameters signed as an empty stringSign {} for a Money Transfer API and [] for an International Transfer API.
Wrong key pairMake sure the public key registered with Flip comes from the private key you sign with. Check the environment: Sandbox and Production keys are registered separately.
Signature Base64 is URL-safe or has line breaksUse standard Base64 (+ and /) on one line.

If you still get errors, email [email protected] with your Flip for Business email account, the endpoint, the request time, the payload string you signed, and the signature you sent. Never include your private key.

Rotating Your Key​

To replace your key pair, for example on a schedule or after a suspected leak:

  1. Generate a new key pair (Step 1).
  2. Email the new public key to [email protected] or [email protected] with the subject Request setup Public Key and Enable signature feature, and say in the email that this is a key rotation.
  3. Agree on a switch-over time with the Flip team. When the new public key is active, signatures made with the old private key are rejected, so start signing with the new private key at the agreed time.
  4. Securely delete the old private key.