Authorization Flow
This guide explains how SaaS platforms use Wonder BizConnect's QR code authorization flow to connect merchant accounts. The flow is secure, fast, and requires no credential sharing.
Before You Start
Before integrating, make sure you have received the following from the Wonder team:
| Credential | Description |
|---|---|
client_id | Public client ID used for Basic Authentication |
client_secret | Client secret — store only on your server, never in frontend code |
app_id | Your SaaS App ID |
| Scopes | The fixed permission set for your App, configured by the Wonder team |
| App type | Always PUBLIC for SaaS platforms (any merchant can authorize) |
The client_secret must never be exposed to browsers, mobile apps, or client-side code. All BizConnect API calls from your platform must go through your backend.
Flow Overview
SaaS Backend BizConnect API Merchant (Wonder App)
| | |
1 |-- client_id/secret -->| Get app token |
|<-- app token ---------| |
| | |
2 |-- Create QR code ---->| Generate QR (PENDING) |
|<-- content + | |
| server_code | |
| | |
3 | Show QR code to | |
| merchant in your | |
| admin dashboard | |
| | |
4 | |<---- Scan QR code ---- |
| | |
5 | |< Approve authorization |
| | Write grant + issue |
| | user_grant token |
| | QR -> SUCCESS |
| | |
6 |-- Poll QR status -----| |
|<-- SUCCESS + token ---| |
| | |
7 | Save token & | |
| refresh_token | |
| for the merchant | |
Step 1: Get an App Token
Your backend exchanges client_id and client_secret for a short-lived app token. This token is only used to create and poll QR codes — it cannot call business APIs.
curl -X POST 'https://api-stg.wonder.app/api/v1/biz/token' \
-u '{{CLIENT_ID}}:{{CLIENT_SECRET}}'
Response:
{
"access_token": "<biz_app JWT>",
"token_type": "Bearer",
"expires_in": 1800,
"app_id": "{{APP_ID}}",
"env": "UAT"
}
Cache the app token on your server and reuse it until it expires. Avoid requesting a new token for every QR code creation.
Step 2: Create a QR Code
Call the QR code creation endpoint with the app token. Each merchant authorization attempt gets its own QR code.
curl -X POST 'https://api-stg.wonder.app/api/v1/biz/login/qrcodes' \
-H 'Authorization: Bearer {{APP_TOKEN}}' \
-H 'Content-Type: application/json' \
-d '{"device_id": "{{MERCHANT_ID_IN_YOUR_SYSTEM}}"}'
Request body:
| Field | Required | Description |
|---|---|---|
device_id | No | A unique identifier for this merchant session in your system (max 64 chars). Recommended: use your internal merchant/tenant ID. Enables multi-merchant isolation. |
Response (201 Created):
{
"content": "https://wonder.app/biz-qr?user_code=<24-char-code>",
"server_code": "<64-char-handle>",
"status": "PENDING",
"device_id": "merchant_12345",
"expires_at": "2026-11-09T03:20:00Z",
"expires_in": 120,
"app": {
"id": "{{APP_ID}}",
"name": "Your SaaS Platform",
"developer": "Your Company",
"scopes": ["<scope_1>", "<scope_2>"]
}
}
Key fields:
| Field | Description |
|---|---|
content | Render this as the QR code shown to the merchant. Do not modify or concatenate it. |
server_code | Use this to poll for the authorization result. Keep it server-side — do not show it to users. |
expires_in | QR code validity in seconds (default 120s / 2 minutes). |
Step 3: Display the QR Code to the Merchant
Render the content string as a QR code in your platform's admin dashboard or settings page. Suggested UI:
- Headline: "Connect your Wonder account"
- Subtitle: "Open the Wonder App and scan this QR code to authorize"
- App info: Show your app name and the scopes you're requesting (transparency builds trust)
- Expiry countdown: Show a timer based on
expires_in, with a "Refresh QR code" button
Step 4: Merchant Scans & Approves
The merchant opens their Wonder App, scans the QR code, and sees an authorization confirmation page showing:
- Your SaaS App name and developer
- The list of permissions (scopes) being requested
- QR code expiry time
The merchant taps Approve to grant access, or Reject to decline.
For SaaS platform apps, the scopes shown on the confirmation page are fixed per App. All merchants see the same permission list. This is by design — merchants know exactly what they're authorizing.
Step 5: Poll for Authorization Result
Your backend polls the QR code status endpoint using server_code. We recommend polling every 2 seconds.
curl 'https://api-stg.wonder.app/api/v1/biz/login/qrcodes/{{SERVER_CODE}}' \
-H 'Authorization: Bearer {{APP_TOKEN}}'
QR Code States
| Status | Meaning | Action on your side |
|---|---|---|
PENDING | Waiting for scan or approval | Keep polling |
SUCCESS | Merchant approved | Stop polling, save tokens |
REJECTED | Merchant declined | Stop polling, show message |
EXPIRED | QR timed out | Stop polling, offer refresh |
SUCCESS Response
{
"server_code": "<handle>",
"status": "SUCCESS",
"expires_at": "2026-11-09T03:20:00Z",
"user_id": "<wonder-user-id>",
"business_id": "<business-id>",
"user": {
"id": "<wonder-user-id>",
"name": "Store Manager",
"email": "manager@store.com"
},
"business": {
"id": "<business-id>",
"business_name": "Cafe Wonder",
"business_legal_name": "Cafe Wonder Ltd."
},
"jti": "<session-jti>",
"device_id": "merchant_12345",
"token": "<user_grant JWT>",
"refresh_token": "<refresh-token>",
"token_expired_at": "2026-11-09T03:50:00Z",
"refresh_token_expired_at": null
}
Key fields for SaaS platforms:
| Field | Description |
|---|---|
business.id | The merchant's Wonder business ID. Store this mapped to your internal merchant ID. |
business.business_name | Display name of the merchant business. |
token | The user_grant token used to call business APIs. |
refresh_token | Used to get a new user_grant token when it expires. null means never expires. |
token_expired_at | When the access token expires. |
Save token, refresh_token, business_id, and token_expired_at in your database, linked to the merchant's account in your system. Each merchant has their own token set.
Step 6: Refreshing Tokens
When the user_grant token is about to expire, use the refresh_token to get a new one — no re-scanning required.
curl -X POST 'https://api-stg.wonder.app/api/v1/biz/token/refresh' \
-u '{{CLIENT_ID}}:{{CLIENT_SECRET}}' \
-H 'Content-Type: application/json' \
-d '{"refresh_token": "{{REFRESH_TOKEN}}"}'
Response:
{
"access_token": "<new user_grant JWT>",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "<same refresh_token>",
"token_expired_at": "2026-11-09T04:20:00Z",
"refresh_token_expired_at": null
}
Key points:
refresh_tokendoes not rotate — you can reuse it within its validity period- The old
user_granttoken is immediately invalidated after refresh - If
refresh_token_expired_atisnull, the refresh token never expires
Step 7: Merchant Revokes Authorization
Merchants can revoke authorization at any time from the Wonder App. When this happens:
- All active sessions for that merchant are immediately invalidated
- Your API calls will return
TOKEN_REVOKED(401) orGRANT_REVOKED(409) - You should clean up the stored tokens for that merchant and show a "Reconnect" prompt
Recommended behavior: when you receive a revocation error, mark the merchant's connection as "disconnected" and prompt them to go through the QR authorization flow again.
Error Handling
| HTTP Status | Error Code | Scenario | Recommended Action |
|---|---|---|---|
| 401 | INVALID_CLIENT | Invalid client_id / secret | Check credentials |
| 401 | INVALID_TOKEN | App token expired / invalid | Get a new app token |
| 404 | QRCODE_NOT_FOUND | server_code doesn't exist or belongs to another app | Create a new QR code |
| 409 | QRCODE_NOT_PENDING | QR already processed (success/rejected/expired) | Create a new QR code |
| 409 | GRANT_REVOKED | Merchant revoked authorization | Disconnect merchant, prompt re-auth |
| 401 | TOKEN_REVOKED | User token was revoked | Use refresh_token, or prompt re-auth |
| 429 | RATE_LIMITED | Rate limit hit | Back off and retry (see Retry-After header) |
Security Best Practices
client_secretstays on the server — never embed it in frontend code, mobile apps, or logs- Use HTTPS for all API calls, in all environments
- Cache tokens — reuse app tokens and user tokens until they expire
- Encrypt stored tokens —
refresh_tokenanduser_granttokens should be encrypted at rest - Stop polling on terminal state — don't keep polling after SUCCESS / REJECTED / EXPIRED
- Handle revocation gracefully — provide a clear "Reconnect Wonder Account" flow in your UI
Checklist
- You have received
client_idandclient_secretfrom Wonder - Your backend can get an app token via
/api/v1/biz/token - Your backend can create QR codes and render the
contentfield - Your frontend displays the QR code with app name and scopes
- Your backend polls QR status and handles all states (PENDING / SUCCESS / REJECTED / EXPIRED)
- On SUCCESS, you save
token,refresh_token, andbusiness_idmapped to the merchant - Token refresh works without merchant interaction
- Revocation errors (TOKEN_REVOKED / GRANT_REVOKED) are handled gracefully
- Production credentials and base URL are configured correctly