API Reference
HMAC Signer
Optional HMAC-SHA256 signing for every outbound OnePay request. Server-side only.
HmacSigner (in packages/paynow_core/lib/src/hmac_signer.dart) attaches four headers to every request the SDK sends:
X-PayNow-Client-Id: <your client_id>
X-PayNow-Timestamp: <unix seconds, UTC>
X-PayNow-Nonce: <16 random bytes, base64>
X-PayNow-Signature: <hex(hmac_sha256(secret, canonical_string))>
Canonical string
<unix_seconds>\n<nonce>\n<METHOD>\n<path>\n<sha256_hex(body)>
The five fields are joined by literal \n (newline). The body field is the SHA-256 hex digest of the exact bytes written to the wire — pass the same string to sign() that you hand to the HTTP client.
The verifier on the receiving side must reproduce this string verbatim.
Constructing one
final signer = HmacSigner(
clientId: env['PAYNOW_HMAC_CLIENT_ID']!,
secret: env['PAYNOW_HMAC_SECRET']!,
);
Then pass it to the OnePay config:
PayNowOnePayConfig(
baseUri: ...,
apiKey: ...,
secretKey: ...,
signer: signer,
);
Every outbound request now gets the four headers automatically.
Generate a secret
openssl rand -base64 32
Server-only — no exceptions
The HMAC secret must never reach a browser. Embedding it in a JS bundle makes it readable by anyone who downloads your gateway page, defeating the entire purpose.
The repo enforces this in two ways:
payment_api_factory.dartdeliberately has noString.fromEnvironmentdefaults forhmacClientId/hmacSecret. They have to be passed explicitly from server-side code.- The local stack script (
scripts/start_local_stack.sh) passes HMAC credentials as process environment variables to the merchant server, not as--dart-defineflags. The merchant server reads them viaPlatform.environmentat request time.
If you need merchant-credential auth (Api-Key / Secret-Key) in the browser bundle for a dev iteration, that's tolerable for paynowdev only. HMAC is non-negotiable: server-side or not at all.
Backend verification
The Spring HMAC filter on the OnePay side reproduces the canonical string and compares signatures with constant-time equality. If a request fails verification it's rejected with 401 and an empty body — the SDK surfaces that as code: GATEWAY_AUTH_REJECTED.
Clock skew
The server's verifier rejects requests whose timestamp is more than 5 minutes off. Make sure your server's clock is NTP-synced — a drifting clock will manifest as random 401s after a redeploy.
Nonce uniqueness
HmacSigner uses Random.secure() for the nonce. The verifier may also keep a short-lived nonce cache to reject replays — the SDK doesn't help with that side; just don't reuse nonces, which Random.secure() already guarantees probabilistically.