Required headers
Content-Type— exact content type included in the signature.X-Client-ID— API Key identifying the signing credential.X-Timestamp— request time as Unix milliseconds.X-Nonce— 32-character lowercase hexadecimal nonce.X-Content-Hash— lowercase SHA-256 hex digest of the raw body.X-Signature— lowercase HMAC-SHA256 hex signature.
X-Signature is not part of canonical headers. The other five headers are signed. Use the exact received Content-Type value after trimming surrounding whitespace; do not normalize or replace it.
Signature formula
- Calculate
content_hash = lowercase_hex(SHA256(raw_request_body)). - Build
StringToSignwith the rules below. - Calculate
signature = lowercase_hex(HMAC-SHA256(api_secret, StringToSign)). - Compare the calculated value with
X-Signatureusing a constant-time comparison.
StringToSign
Join these seven components with one line feed (\n) in this exact order. Do not append a trailing line feed.
- Uppercase HTTP method
- Normalized URL path
- Canonical query
- Canonical headers
- Content hash
- Timestamp
- Nonce
Canonicalization rules
Method
Convert the HTTP method to uppercase. Webhook deliveries usePOST.
Path
- Use only the callback path; exclude scheme, host, query, and fragment.
- Normalize an empty path to
/. - Preserve a trailing slash exactly.
- ACCOUNT transaction deliveries sign the decoded URL path.
- USER transaction, KYB, and MED deliveries sign the escaped path.
/pagsmile/webhooks.
Query
- Parse the raw query and sort parameter names lexically.
- Preserve the original value order for repeated names.
- Encode names and values using Go
url.QueryEscapebehavior; spaces become+. - Join encoded
name=valueitems with&. - When no query exists, keep the empty query line.
- If parsing fails, use the unmodified raw query.
z=9&b=2&a=hello world becomes a=hello+world&b=2&z=9.
Headers
Sign these lowercase header names in lexical order:content-typex-client-idx-content-hashx-noncex-timestamp
lowercase-header-name:trimmed-value. Do not include host, content-length, user-agent, or x-signature.
Body
Calculate SHA-256 over the exact raw bytes received from the network. Do not parse and reserialize JSON before hashing it. Whitespace, property order, escaping, encoding, and line endings all affect the result.Verification order
- Read and retain the raw request body.
- Require all six signature headers.
- Resolve the API Secret associated with
X-Client-ID. - Parse
X-Timestampand enforce a freshness window. - Hash the raw body and compare it with
X-Content-Hash. - Rebuild the canonical query, canonical headers, and
StringToSign. - Calculate HMAC-SHA256 with the API Secret.
- Compare with
X-Signaturein constant time. - Atomically reject a reused
X-Client-IDandX-Noncepair. - Parse and process the event only after verification succeeds.