NUMU Docs
Contact
APIThemesPartner Apps
APIThemesPartner Apps
  1. Partner Apps
  • 🧩 Partner apps
  • Join the partner program
  • Register your app
  • App manifest
  • OAuth and app tokens
  • App webhooks
  • Review and publishing
  • The numu CLI
  • Build with an AI assistant
  • Troubleshooting apps
  1. Partner Apps

OAuth and app tokens

A merchant approves your app once. Your server exchanges a code for a token that works on that store until the app is uninstalled.

The install flow#

1
The merchant approves
The merchant clicks Install on your app's page in the App Store. NUMU shows its consent screen: your app, your partner name, and every scope as a plain sentence. Only the store's owner can approve.
The Install button asks for your manifest's oauth.scopes and returns to the first URL in oauth.redirect_urls.
2
NUMU redirects to your server
https://app.example.com/numu/callback?code=numu_code_…&store_id=3fa85f64-5717-4562-b3fc-2c963f66afa6&state=…&timestamp=1767225600&hmac=6f3c…c81
ParameterIs
codeSingle use, valid for 10 minutes
store_idThe store that installed your app
stateThe value from the consent link
timestampUnix seconds, when NUMU signed the redirect
hmacThe signature over the other parameters
If the merchant cancels, you get ?error=access_denied&state=…, with no code and no signature.
3
Verify it
Verify the hmac and reject a timestamp older than 5 minutes, before anything else.
state protects the flows you start yourself: when you sent the merchant to the consent screen (Ask for more scopes), check that state is the value you sent. When the merchant started from the App Store, NUMU chose state, and the hmac is what proves the redirect is genuine.
4
Exchange the code
Call this from your server, never from a browser: it carries your client secret.
cURL
JavaScript
Python
PHP
{
  "success": true,
  "data": {
    "access_token": "numu_app_…",
    "scopes": ["orders:read", "orders:write"],
    "store_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }
}
5
Save the token
Store the token encrypted, keyed by store_id. Then send the merchant to a page of your app, in Arabic unless they chose English.

Verify signed links#

NUMU signs two links with your client secret: the install redirect and Open app. Both use the same scheme:
1.
Take every query parameter except hmac, URL-decoded.
2.
Sort them by name and join them as name=value, separated by &.
3.
Compute HMAC-SHA256 of that string with your client secret, as lowercase hex.
4.
Compare the result with hmac in constant time, and reject a timestamp older than 5 minutes.
For the redirect above, the signed string is code=numu_code_…&state=…&store_id=3fa85f64-…&timestamp=1767225600.
JavaScript
Python
PHP
This is not the webhook scheme: there is no t= prefix and no body. Two rules keep the signed string predictable:
Make state from letters, digits, - and _ only.
Give app_url no query string of its own. NUMU signs only the parameters it adds.

When the exchange fails#

The error comes back in the usual envelope, for example {"success": false, "error": {"code": "HTTP_ERROR", "message": "invalid client credentials"}}.
Statuserror.messageWhat to do
401invalid client credentialsCheck client_id and client_secret. After a rotation, only the new secret works
400invalid, expired or already used codeA code works once and for 10 minutes. Send the merchant through the consent screen again
400grant_type must be authorization_codeLeave out grant_type, or send authorization_code

Call the API#

Send the token as a bearer token, on the store it belongs to:
Every store route carries the store: https://numueg.app/api/v1/stores/{store_id}/…. A token works only on its own store; any other store_id answers 403.
GET /auth/api-key/me tells you what a token is: app_slug, store_id, store_name, subdomain, currency, default_language and scopes. Call it at startup.
Everything else works as for any integration: pagination, rate limits (300 requests a minute per token), Idempotency-Key on writes, errors.

Token lifetime#

An app token has no expiry. It stops working, with 401 and Invalid or revoked app token, when:
the merchant uninstalls your app, or disables it;
NUMU suspends your app, or pauses all partner apps;
you exchange a newer code for the same store: the old token keeps working for 24 hours, then stops;
you revoke it.
Rotating your client secret does not affect tokens.
Treat a 401 on one store as an uninstall of that store: stop its jobs and stop calling the API for it.

Ask for more scopes#

Send the merchant to the consent screen yourself when you need an optional scope, when a new version added scopes, or to reconnect a store:
https://merchant.numueg.app/oauth/authorize?client_id=…&store_id=…&redirect_uri=https%3A%2F%2Fapp.example.com%2Fnumu%2Fcallback&scope=orders%3Aread%20orders%3Awrite%20catalog%3Aread&state=…
scope: space-separated. It must include every scope in oauth.scopes, and may add any from oauth.optional_scopes.
redirect_uri: one of your redirect_urls, byte for byte.
state: required. Make a new one for each request, and check it when the merchant returns.
store_id: the store. The merchant must be signed in to NUMU as its owner.
The merchant sees only what is new, approves, and returns to your callback with a new code. The flow goes on as above. The new token replaces the old one, which keeps working for 24 hours so you can switch without a gap.

Test on a development store#

Before your app is published, the App Store does not list it, and it installs only on your own development stores. Start the install yourself:
1.
Find your development store's id: see Find the id of a development store.
2.
Signed in to the dashboard as yourself, open the consent link for that store:
https://merchant.numueg.app/oauth/authorize?client_id=numu_ci_…&store_id=<development store id>&redirect_uri=<one of your redirect_urls, URL-encoded>&scope=<your scopes, space-separated>&state=<random>
3.
Approve. Your callback receives a code, exactly as it would from a merchant's install.
The Install on a development store button in the partner portal adds the app without a token, so it does not test your server.

Open app#

Your app's page in the merchant's dashboard has an Open app button. It opens your app_url with signed parameters:
https://app.example.com/numu?store_id=3fa85f64-5717-4562-b3fc-2c963f66afa6&locale=ar&timestamp=1767225600&hmac=…
Verify it like the install redirect. It proves that the merchant came from their NUMU dashboard moments ago. It is not a login: look up your installation by store_id, then start your own session. Show the page in Arabic when locale=ar.

Your client secret#

It is shown once, when you register the app. Keep it in your server's environment, never in code or in a browser.
It signs the install redirect, Open app and every webhook, and it authenticates /oauth/token and /oauth/revoke.
Rotate it in the partner portal, on your app's page. The old secret stops working at once, and everything NUMU signs from then on uses the new one, so deploy the new secret right away. Tokens keep working.

Revoke a token#

It answers 200, with data.revoked set to true, even for a token it does not know. So it cannot be used to test whether a token is valid.
Modified atĀ 2026-09-24 14:17:29
Previous
App manifest
Next
App webhooks
Built with