PPayNow Docs
Menu — HMAC Signer

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:

  1. payment_api_factory.dart deliberately has no String.fromEnvironment defaults for hmacClientId / hmacSecret. They have to be passed explicitly from server-side code.
  2. The local stack script (scripts/start_local_stack.sh) passes HMAC credentials as process environment variables to the merchant server, not as --dart-define flags. The merchant server reads them via Platform.environment at 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.