Authentication
Migo APIs use two separate authentication schemes depending on the surface you are calling:
| Scheme | Where it applies | Header / placement |
|---|---|---|
| Merchant token (long-lived secret) | Payment Links (POST /transactions) and Alternative Payments (POST /api/v1/integrations/transactions[/{uid}/payments|/payment-intents|/dismiss]) | Sent in the Authorization header β see Header format by endpoint below |
Body credentials (privateKey + publicKey) | POST /transactions-hook only (browser-friendly Payment Links) | Inside the JSON body |
A separate JWT Bearer scheme is used by the Wallet and CMS surfaces (end-user and backoffice flows). If you are integrating Payment Links or Alternative Payments, the merchant token is all you need β no login round-trip is required. Jump to Merchant token below.
Header format by endpointβ
The same merchant token authenticates both surfaces. The shared token authorizer strips an optional Bearer prefix before looking the token up, so the token is accepted with or without the prefix. The examples below use the conventional format for each surface.
| Endpoint family | Example | Header format |
|---|---|---|
| Payment Links | POST /transactions | Authorization: <token> (commonly sent without a scheme prefix) |
| Alternative Payments | POST /api/v1/integrations/transactions, POST /api/v1/integrations/transactions/{uid}/payments, POST /api/v1/integrations/transactions/{uid}/payment-intents, POST /api/v1/integrations/transactions/{uid}/dismiss | Authorization: Bearer <token> (Bearer scheme prefix) |
It is the same merchant token on both endpoint families. The authorizer normalizes an optional Bearer prefix before matching the token, so you can keep the same value and add or omit the prefix.
Merchant tokenβ
The merchant token is a long-lived secret issued by Migo when your application is registered. A single token authenticates two endpoint families β Payment Links and Alternative Payments β but the conventional header format differs per family. See Header format by endpoint above for the canonical matrix.
Payment Links β Authorization: <token> (no prefix)β
The Payment Link entry point POST /transactions validates Authorization by matching it against the merchant record (the authorizer strips an optional Bearer prefix first). The conventional form sends the token as the entire value of the header:
Authorization: <your-merchant-token>
curl -X POST https://sb-mw.migopayments.com/transactions \
-H 'Content-Type: application/json' \
-H 'Authorization: b6a615d8...0ba65c1' \
-d '{ "amount": 1, "userId": "+50224865444", "channel": "wa", "client": "your-client-slug", "ads": [] }'
Alternative Payments β Authorization: Bearer <token> (Bearer prefix)β
The Alternative Payments routes (/api/v1/integrations/transactions[β¦]) accept the same merchant token, conventionally sent with a Bearer prefix:
Authorization: Bearer <your-merchant-token>
# Same token as above, different header format
curl -X POST https://sb-mw.migopayments.com/api/v1/integrations/transactions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer b6a615d8...0ba65c1' \
-d '{ "amount": 50, "channel": "web", "client": "your-client-slug", "userId": "+50224865444" }'
Why two conventions?β
Payment Links predate the Alternative Payments service; they each landed with different documented conventions, so the examples differ by surface. The token itself is identical, and the shared authorizer accepts it with or without a Bearer prefix. The same convention is shown in Create a Payment Link, in the Sandbox cheat sheet, and in Alternative Payments β Overview.
Lifetime and rotationβ
- The merchant token is long-lived. There is no merchant-facing refresh flow.
- Treat it as a database password: store it in your secret manager, never embed it in client-side code, and rotate via your Migo contact whenever the secret may have been exposed.
- Sandbox and production tokens are different β you cannot use a sandbox token against
https://mw.migopayments.com.
Body-credential alternative (/transactions-hook)β
Integrations that cannot keep a token server-side (single-page apps, embedded widgets) can instead authenticate POST /transactions-hook by sending privateKey + publicKey in the JSON body. The endpoint hashes the pair to identify your application, and it enables CORS so a browser can call it directly. See Create a Payment Link β alternative endpoint.
JWT Bearer (Wallet & CMS surfaces)β
The Wallet and CMS surfaces use Ed25519-signed JWTs instead of the merchant token. The flow is similar across both, but each has its own login endpoint and its own token. You only need this section if you are integrating those surfaces β Payment Links and Alternative Payments do not use it.
Token flowβ
- Log in with credentials to receive an access token + refresh token.
- Attach the access token as
Authorization: Bearer <token>to every request. - Refresh when the access token expires β do not re-log-in unless the refresh token is also expired.
Access and refresh tokens are short-lived relative to the merchant token. The login response returns the issued tokens. Token lifetimes are configurable; some login and refresh calls accept optional accessTokenTtl and refreshTokenTtl fields (in seconds) to override the issued lifetimes. Tokens are signed with Ed25519; the public key is stable per environment. Contact Migo for your integration's key pair.
Wallet loginβ
Two separate login flows β pick based on caller type:
# Application login (used by backend services calling the gateway)
POST https://api.ali.app/rest/auth/login
Content-Type: application/json
{
"merchant": "YOUR_MERCHANT_SLUG",
"signature": "<ed25519_sign(merchant) in base64>"
}
The application proves its identity with an Ed25519 signature challenge: sign your merchant identifier (the plain merchant string) with your private key and send the base64 signature. The gateway verifies it against your registered public key and returns the application access + refresh tokens. There is no applicationId/secret pair.
# End-user login (used by the wallet app on behalf of the cardholder)
POST https://api.ali.app/rest/users/login
Content-Type: application/json
Authorization: Bearer <application-access-token>
{
"username": "user@example.com",
"password": "user-password"
}
End-user login is authenticated by the application access token obtained from /auth/login (sent as Authorization: Bearer <token>). The body uses a username field; an optional merchant field supports multi-tenant login.
CMS loginβ
The CMS requires two tokens: an application JWT plus a CMS user JWT.
# 1. Application login
POST https://api.ali.app/cms/rest/app/auth/login
Content-Type: application/json
{ "merchant": "your-merchant-slug" }
# 2. CMS user login
POST https://api.ali.app/cms/rest/app/users/login
Authorization: Bearer <app-access-token>
Content-Type: application/json
{ "email": "operator@partner.com", "password": "..." }
Both calls return access and refresh tokens. Send the application access token as the Authorization: Bearer header for application-scoped routes; the CMS user token is sent the same way for user-scoped routes.
Refreshβ
When the access token expires, refresh instead of re-logging-in.
POST https://api.ali.app/rest/auth/refresh
Content-Type: application/json
{ "refreshToken": "<refresh-token>" }
The response carries a new access token and refresh token.
/auth/refresh refreshes the application/integration access token. The end-user (wallet) session obtained from POST /users/login has its own refresh endpoint:
POST https://api.ali.app/rest/users/refresh
Authorization: Bearer <application-access-token>
Content-Type: application/json
{ "refreshToken": "<user-refresh-token>" }
Optional fields merchant, accessTokenTtl, and refreshTokenTtl may be supplied to override the issued token lifetimes. The response returns a new user access token and refresh token.
OTP (end-user flows only)β
Some end-user flows require an OTP issued over email. All OTP calls require the application JWT (Authorization: Bearer <application-access-token>). The pattern is:
POST /users/auth/otpβ request an OTP. Body:{ "email": "user@example.com", "purpose": "<purpose>" }.- The user enters the 6-digit code received by email.
PUT /users/auth/otpβ submit the code. Body:{ "email": "...", "otp": "123456", "purpose": "<purpose>" }. On success the responsedatacarries the verification result; for thepassword_resetpurpose it includes atokenyou pass to the password-reset call.
The purpose field is a free-form string that must match between the request and verification steps. The documented value for the password-reset flow below is password_reset.
Password resetβ
Resetting an end-user password is a two-step, OTP-gated flow. Both calls require the application JWT (Authorization: Bearer <application-access-token>).
# 1. Request an OTP with purpose "password_reset"
POST https://api.ali.app/rest/users/auth/otp
Authorization: Bearer <application-access-token>
Content-Type: application/json
{ "email": "user@example.com", "purpose": "password_reset" }
# 2. Verify the emailed code β the response data includes a "token"
PUT https://api.ali.app/rest/users/auth/otp
Authorization: Bearer <application-access-token>
Content-Type: application/json
{ "email": "user@example.com", "otp": "123456", "purpose": "password_reset" }
# 3. Reset the password using that token
POST https://api.ali.app/rest/users/auth/password/reset
Authorization: Bearer <application-access-token>
Content-Type: application/json
{
"email": "user@example.com",
"newPassword": "NewStrongP@ssw0rd!",
"confirmPassword": "NewStrongP@ssw0rd!",
"token": "<token-from-otp-verification>"
}
Logging out / ending a sessionβ
End a user session with:
POST https://api.ali.app/rest/users/logout
Authorization: Bearer <application-access-token>
The call invalidates the user's active session. It requires the application JWT.