Skip to main content
Most endpoints require a cryptographic signature so Numero can verify the integrity and authenticity of the request. The signature is HMAC-SHA256 and travels in the X-Numero-Signature header. This is not limited to POST. Several GET endpoints that return tenant data — your balance, transfer status, transaction list, virtual accounts, cards, cardholders, customers and invoices — are signed too, so that a leaked read-only API key cannot read your account without the signing secret. Every page in the API reference states Signature required: Yes/No for its endpoint; that field is generated from the source code, so trust it over any example you find elsewhere.
Two different keys. The X-Numero-Api-Key header identifies you. The Public Key is the HMAC secret you sign with. They are different values, and despite its name the Public Key is a secret — never ship it in client-side code or publish it. Signing with the API key will never produce a valid signature.

How it works

  1. Take the exact JSON body you will send, byte for byte (camelCase property names)
  2. Compute an HMAC-SHA256 hash of it, using the UTF-8 bytes of your Public Key as the secret
  3. Base64-encode the resulting hash
  4. Send it in the X-Numero-Signature header, along with X-Numero-Signature-Version: v2
Your Public Key is used as the HMAC secret as-is — the raw UTF-8 bytes of the key string. Do not Base64-decode it first: that produces a different secret and your signature will never validate.
Sign exactly what you send. The signature is verified against the raw body bytes on the wire. If you serialize the object twice — once to sign and once to send — and the two differ by even a space or key order, verification fails. Build the JSON string once, sign that string, and send that same string.

Code examples

Node.js

Python

C#

PHP

Full request example

Signing GET requests

Signed GET endpoints have no body, so the input is the canonicalized query string:
  1. URL-decode each value once.
  2. Sort parameters by key, case-sensitive (ordinal).
  3. If a key appears more than once, sort its values ordinally too.
  4. Join every pair as key=value, separated by &.
  5. If there is no query string at all, sign the empty string.
So GET /api/v1/business/balance?currency=NGN signs the exact string currency=NGN — not the full URL, and not the path.

Check your implementation offline

You do not need to call Numero to know whether your signing code is right. These vectors are computed with the same HMAC the server uses, so if you reproduce all three you are correct. Using the key pk_test_numero_example_key: Reproduce them from your shell:
There is also a POST /api/v1/business/generatesignature?publickey=... helper. Avoid it. It takes your signing secret in the query string, where it is routinely written to server access logs, proxy and CDN logs, browser history and monitoring tools. Use the offline vectors above instead. If you have already used the helper with a live key, rotate that key.

Legacy headers

Older integrations send the API key as x-api-key and the signature as x-signature-key. Those still work and will keep being accepted, but new integrations should use the canonical X-Numero-Api-Key / X-Numero-Signature headers shown above.

Important notes

  • The Public Key is the HMAC secret, used as its raw UTF-8 bytes — never Base64-decode it
  • Property names in the JSON body must be camelCase
  • Sign the exact bytes you send — no re-serialization between signing and sending
  • Ensure your JSON serialization produces consistent output (no extra whitespace, consistent key ordering)