Signing and verification
The international API uses a tokenless server-side signing model. Ordinary API requests are signed by the merchant for the API service. Ordinary API responses are signed by the API service for the merchant. Webhook delivery requests are also signed by the platform for the merchant. The three flows use different directions, secrets and signing inputs, so implement them separately.
Merchant administrators or explicitly authorized technical users create API credentials in Merchant Portal → International Payments → API Access. The platform generates the API Secret and shows it only once in the current page session after creation. The platform does not accept custom Secrets. After a browser refresh or close, merchants, support personnel and PMS cannot view or recover that Secret again. Store it immediately in a server-side Vault, KMS or controlled environment variable.
For routine rotation, create a replacement Key, run old and new Keys in parallel, confirm cutover using last_used_at, disable the old Key for an observation period, and then revoke it permanently. If the current Secret is unavailable to the operator, or exposure is suspected, create a replacement Key and revoke the old Key.
Signing API requests
An ordinary API request is sent from the merchant backend to the API service. The merchant signs the HTTP request with the API Secret and puts the API Key, timestamp, nonce and signature into separate request Headers.
1. Prepare signing materials
| Material | Description |
|---|---|
| API Key | Merchant API credential used to identify the merchant, Secret and permissions. Source access is controlled by the platform operations security group. |
| API Secret | Server-side secret corresponding to the API Key. Use it only locally; never send it to the API service. |
| HTTP method | Uppercase method, such as POST or GET. |
| Request URL | Path plus original Query without scheme, host, port or fragment. |
| Request timestamp | Unix timestamp text in seconds, normally accepted within ±300 seconds of the API service server time. |
| Request nonce | nonce, unique for every request under the same API Key, maximum 128 characters. |
| Request body | Exact UTF-8 raw Body sent on the wire. Use an empty string for GET requests. |
2. Get the HTTP method
Use the method of the actual API call and uppercase it:
POST3. Get the request URL
The request URL contains only Path and Query. Do not include scheme, host, port or fragment. Use the exact Query order and encoding sent by the client; do not sort, decode or re-encode it.
/intl/v1/payment/order/createQuery example:
/intl/v1/payment/order/query?merchantOrderNo=DEMO_PAY_202606010001For balance query, the request URL depends on the actual optional filters. If no country or currency filter is sent, sign:
/intl/v1/balance/queryIf both filters are sent, sign the actual Query:
/intl/v1/balance/query?country=PH¤cy=PHP4. Generate the request timestamp
Use Unix seconds and send it as Header text. Keep merchant servers synchronized with NTP or an equivalent clock source. The API service rejects stale requests before business processing.
date +%sExample:
17802720005. Generate the request nonce
nonce reduces replay risk. Do not reuse it under the same API Key, including retries.
openssl rand -hex 16Example:
nonce_fixture_0016. Get the request body
For POST requests, sign the exact raw JSON string sent on the wire. Do not change spacing, line breaks, field order or character encoding after signing. If a framework serializes JSON for you, sign the actual bytes it sends.
GET requests have no body. The fifth line is an empty string, and the final newline byte is still required.
7. Build the request signing payload
The request signing payload has exactly five lines. Append one newline byte \n after every line, including the last line. If a value already ends with \n, append the required newline anyway.
HTTP method\n
request URL\n
request timestamp text\n
request nonce\n
request body\nExample:
POST\n
/intl/v1/payment/order/create\n
1780272000\n
nonce_fixture_001\n
{"merchantOrderNo":"DEMO_PAY_202606010001","productCode":"PH_PHP_PAYMENT_QRPH_GCASH","amount":{"value":"100.50"},"orderDescription":"Virtual order"}\n8. Calculate the signature
Use the API Secret to calculate HMAC-SHA256 over the signing payload, encode the digest as lowercase hexadecimal, and prefix it with v1=.
printf 'POST\n/intl/v1/payment/order/create\n1780272000\nnonce_fixture_001\n{"merchantOrderNo":"DEMO_PAY_202606010001","productCode":"PH_PHP_PAYMENT_QRPH_GCASH","amount":{"value":"100.50"},"orderDescription":"Virtual order"}\n' \
| openssl dgst -sha256 -hmac "$INTL_API_SECRET" -hexSignature format:
v1=<64 lowercase hex>9. Build the request signing Headers
Api-Key: ik_live_xxx
Timestamp: 1780272000
Nonce: nonce_fixture_001
Signature: v1=<64 lowercase hex>Notes:
- Never include the API Secret in request Headers.
Timestamp,NonceandSignaturemust match the signed values exactly.- Ordinary API requests must also send standard HTTP media Headers:
Accept: application/json; POST requests must also sendContent-Type: application/json. These two Headers are not included in the signing payload. - Request and response media formats are documented in Basic rules.
- Prefer the SDK. Custom implementations must pass the request-signature test vectors before launch.
- Header names are case-insensitive. The documentation shows canonical unprefixed Header names; do not add product, company or brand prefixes.
Verifying API responses
An ordinary API response is returned from the API service to the merchant. The API service signs the raw response Body with the API Secret corresponding to the request API Key. Merchants should verify the response before JSON parsing and business handling.
1. Read response headers
Ordinary API responses can include these headers:
| Header | Returned when | Description |
|---|---|---|
Content-Type | All ordinary API responses with a JSON body | Fixed as application/json, indicating that the response Body is UTF-8 JSON. It is not included in the response verification payload. |
Request-Id | All ordinary API responses | Request tracking ID for support, log correlation and reconciliation. |
Timestamp | Ordinary API responses where the API Key can be identified | Response signing timestamp text in Unix seconds. |
Nonce | Ordinary API responses where the API Key can be identified | Response random string, unique for this response. |
Signature | Ordinary API responses where the API Key can be identified | v1= plus lowercase HMAC-SHA256 response signature. |
Authentication-failure responses where the API service cannot identify the API Key, for example when Api-Key is missing or badly malformed, might not include signature headers. Keep Request-Id, HTTP status and error code for support correlation.
2. Check timestamp
Check that Timestamp is within the allowed clock skew. The recommended default is 300 seconds. Reject stale signed responses for business-state changes and record Request-Id for troubleshooting. The response signature algorithm is fixed as HMAC-SHA256 with the v1= signature prefix.
3. Capture the raw response body
Verification must use the exact raw Response Body. Do not verify a formatted, escaped, field-reordered or re-serialized JSON string. If the response body is empty, use an empty third line and still keep the final newline byte.
4. Build the response verification payload
The response verification payload has exactly three lines. Append one newline byte \n after every line, including the last line:
response timestamp text\n
response nonce\n
response body\nExample:
1780272000\n
nonce_fixture_response_001\n
{"platOrderNo":"PHI112606010000000001001","status":"PROCESSING"}\n5. Calculate and compare the signature
Use the API Secret corresponding to the request API Key to calculate HMAC-SHA256 over the verification payload, encode it as lowercase hexadecimal, add v1=, and compare it with the Signature header using a constant-time comparison.
If verification fails:
- Do not update the business final status from the response Body.
- Record environment, time, merchant order number, the
Request-Idresponse Header, HTTP status and response Bodyerror.code. - Treat a verified HTTP
5xxcreate response or an unverifiable response as an unknown result: query by the originalmerchantOrderNo; if repeated queries still find no order, resend only the unchanged create request with that same order number. - Check whether a proxy, gateway or framework dropped headers, changed the Body, decompressed it or re-encoded it.
Verifying Webhook requests
Webhook delivery is a platform to merchant HTTP request. In this section, "verification" means the merchant verifies the platform's Webhook request. The merchant's HTTP response to the platform is not signed.
1. Read Webhook request headers
Webhook requests use standalone delivery headers. The signature Header names remain consistent with ordinary API request and response signing:
| Header | Description |
|---|---|
Content-Type | Fixed as application/json, indicating that the Webhook Body is UTF-8 JSON. It is not included in the Webhook verification payload. |
Request-Id | Tracking ID for this delivery request. |
Webhook-Secret-Id | Webhook Secret identifier used for this delivery. Use it to choose the locally stored Webhook Secret before verifying Signature. Merchant Portal shows it when the Secret is generated or rotated. |
Timestamp | Delivery signing timestamp text in Unix seconds. |
Nonce | Delivery random string; retries can use a new value. |
Signature | v1= plus lowercase HMAC-SHA256 notification signature. |
2. Select the Webhook Secret
Use Webhook-Secret-Id to select the exact locally stored Webhook Secret. The merchant must save the mapping shown by Merchant Portal, for example whsecid_old_001 -> whsec_old and whsecid_new_002 -> whsec_new. Reject the delivery if the Secret ID is unknown, disabled or belongs to a different merchant environment.
3. Check timestamp and deduplication key
Check Timestamp before verification. The recommended clock-skew limit is 300 seconds. Retries can use new Request-Id, Timestamp, Nonce and Signature values, while Body eventId keeps the same event ID. Deduplicate by Body eventId. Header Timestamp is the delivery signing time and is sent as text; Body order.updateTime is the notified status effective time, is returned as a JSON number, and is stable for the same event.
4. Capture the raw Webhook body
Webhook verification must use the exact UTF-8 raw Body. Do not parse JSON, pretty-print JSON, replace line breaks or change field order before verification. Many frameworks consume the Body stream once, so cache raw bytes in middleware or a filter before parsing.
5. Build the Webhook verification payload
The Webhook verification payload has exactly three lines. Append one newline byte \n after every line, including the last line:
delivery timestamp text\n
delivery nonce\n
Webhook request body\nExample:
1780272000\n
nonce_fixture_webhook_payment_001\n
{"eventId":"evt_fixture_001","eventType":"PAYMENT_SUCCEEDED","merchantNo":"MCH_FIXTURE_001","order":{"platOrderNo":"PHI112606010000000001001","merchantOrderNo":"DEMO_PAY_202606010001","status":"SUCCEEDED","productCode":"PH_PHP_PAYMENT_QRPH_GCASH","country":"PH","amount":{"value":"100.50","currency":"PHP"},"referenceNo":"REF-20260601-0001","customData":{"cartId":"CART-10001","customerRef":"CUST-90001"},"createTime":1780272000,"updateTime":1780275600}}\n6. Calculate and compare the signature
Use the Webhook Secret selected by Webhook-Secret-Id to calculate HMAC-SHA256 over the verification payload, encode it as lowercase hexadecimal, add v1=, and compare it with the Signature header using a constant-time comparison.
Only parse JSON after verification succeeds, and process the event idempotently by Body eventId. If verification fails, the Webhook Secret ID is unknown or the Body is modified, return 4xx or 5xx and do not update order state.
7. Acknowledge the delivery
Return any HTTP 2xx only after the event is durably stored or accepted into a reliable queue. No business response body is required. The platform treats any 2xx response as successful receipt; non-2xx responses, timeouts or network failures trigger retries. For long-running business processing, verify, deduplicate and persist first, then process asynchronously.
