SDK & sessions
The endpoints our web, iOS, Android, React Native and Flutter SDKs call. They are safe to call from a browser or app because they never use your client secret. You only need this page if you are building your own client or want to understand what the SDK does.
Two ways for your frontend to authenticate
| Publishable key | Session token | |
|---|---|---|
| Header | X-QuickAuth-Key: pk_live_… | Authorization: Bearer … |
| Where it comes from | Dashboard → Settings → Publishable key | Your backend calls Create a session token |
| Lifetime | Until you revoke it | 1 hour |
| Best for | Mobile apps | Websites, or when you want no long-lived key in the frontend |
| Rate limit | 1,000/hour per platform (300/hour from browsers) | Per-number OTP limit only |
If you send both headers, the publishable key is used. To stop a key being used from apps you do not own, turn on app restriction for the key in the dashboard and register your Android package name, iOS bundle id or website origin. Mobile SDKs send X-QuickAuth-Package (Android) or X-QuickAuth-Bundle (iOS) so the key can be checked.
Always confirm on your backend
requestId to your backend (our SDKs return it; if you call the API yourself you can send the idToken instead) and call Confirm a verification with your client secret before creating the user’s session.Create a session token
/v1/sdk/sessionYour backend calls this to get a short-lived token for your website or app. The frontend then calls the SDK endpoints with Authorization: Bearer <sessionToken> instead of a publishable key. Use this when you do not want a long-lived key in your frontend code.
No request body.
AuthServer keyX-Client-Id + X-Client-Secret
Response · 200
sessionTokenstringSend it as Authorization: Bearer … on SDK calls.expiresInintegerSeconds until it expires — 3600 (1 hour).tokenTypestringAlwaysBearer.
Errors
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
curl -X POST "https://api.quickauth.in/v1/sdk/session" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET"{
"sessionToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJxYV9saXZlXzhmMmsxbSIsInNjb3BlIjoic2RrIn0…",
"expiresIn": 3600,
"tokenType": "Bearer"
}Refresh a session token
/v1/sdk/session/refreshIssues a new session token. It behaves exactly like Create a session token — you do not send the old token — and exists so SDKs have a clearly named call to make before the hour runs out.
AuthServer keyX-Client-Id + X-Client-Secret
Response · 200
sessionTokenstringSend it as Authorization: Bearer … on SDK calls.expiresInintegerSeconds until it expires — 3600 (1 hour).tokenTypestringAlwaysBearer.
Errors
- 401INVALID_CLIENT_CREDENTIALSX-Client-Id or X-Client-Secret is missing, unknown or wrong.
- 401CLIENT_INACTIVEYour account has been deactivated.
curl -X POST "https://api.quickauth.in/v1/sdk/session/refresh" \
-H "X-Client-Id: YOUR_CLIENT_ID" \
-H "X-Client-Secret: YOUR_CLIENT_SECRET"{
"sessionToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJxYV9saXZlXzhmMmsxbSIsInNjb3BlIjoic2RrIn0…",
"expiresIn": 3600,
"tokenType": "Bearer"
}Send an OTP (from your app)
/v1/sdk/auth/initiateThe frontend version of Send an OTP. Our SDKs call it for you; call it directly only if you are building your own client. Your account’s default template, sender and code length always apply.
AuthPublishable keyX-QuickAuth-Key
Request body
phonestringrequiredCountry code + number, digits only, optional leading+:+919876543210. No spaces or dashes. Indian numbers must be mobiles.channelstringoptionaldefaultautoLower case is fine. Any other value is treated as sms.auto— your dashboard’s primary channelsmswhatsapp— (or wa)emaildeviceTokenstringoptionalToken from this device’s last successful login, for OneTap. Ignored in browsers.deviceInfoobjectoptionalFree-form device details. Max 4 KB.
Response · 200
stateenumOTP_SENT— show the code screenVERIFIED— OneTap approved the device; no code sentsessionIdstringThe verification id. Send it to Verify. (It is the same value the server API callsrequestId.)expiresInintegerSeconds the code stays valid. Default 300.deviceTokenstring | nullSave it on the device for OneTap next time. Null in browsers.
Errors
- 400INVALID_REQUESTPhone missing or not valid, e.g.
phone: phone must be E.164. - 400TEMPLATE_NOT_FOUNDNo default OTP template set up for this channel.
- 400DLT_SENDER_NOT_FOUNDNo default SMS sender id set up.
- 401INVALID_CLIENT_CREDENTIALSPublishable key unknown, app not on the key’s allowed-apps list, or the session token is missing, invalid or expired.
- 429RATE_LIMIT_EXCEEDEDThe publishable key went over its hourly cap (1,000 per hour per platform, 300 for web).
- 402INSUFFICIENT_BALANCEWallet is empty (or SMS / WhatsApp credits are 0).
- 409PROVIDER_ERRORNo connected number for the channel.
- 413INVALID_REQUESTdeviceInfo is larger than 4 KB.
- 429RATE_LIMIT_EXCEEDEDMore than 10 new codes for this number in the last hour.
- 500INTERNAL_ERRORUnexpected server error, including a body that is not valid JSON. Retry; if it persists, send us the requestId.
curl -X POST "https://api.quickauth.in/v1/sdk/auth/initiate" \
-H "X-QuickAuth-Key: pk_live_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919876543210",
"channel": "auto"
}'{
"state": "OTP_SENT",
"sessionId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"expiresIn": 300,
"deviceToken": "a41c0f9e2b7d4c6a8e1f3b5d7c9a0e2f4b6d8c1a3e5f7b9d0c2e4a6f8b1d3c5e"
}Verify an OTP (from your app)
/v1/sdk/auth/verifyChecks the code the user typed. A wrong code returns 200 with state: OTP_FAILED. On success, send the requestId (or the idToken) to your backend and confirm it with Confirm a verification before you create the user’s session.
AuthPublishable keyX-QuickAuth-Key
Request body
sessionIdstringrequiredFrom Send an OTP.codestringrequiredThe code the user entered.deviceTokenstringoptionalMarks this device as trusted for OneTap when the code is right.deviceInfoobjectoptionalMax 4 KB.
Response · 200
stateenumVERIFIEDOTP_FAILED— wrong code; message says how many tries are leftverifiedbooleanSame asstate === "VERIFIED".requestIdstringmessagestringidTokenstring | nullProof of verification for your backend. Valid 5 minutes. Null on a wrong code.
Errors
- 400INVALID_REQUESTsessionId or code missing.
- 400TRANSACTION_NOT_FOUNDUnknown sessionId.
- 400OTP_EXPIREDThe code expired. Send a new one.
- 400MAX_ATTEMPTS_EXCEEDEDToo many wrong codes. Send a new one.
- 401INVALID_CLIENT_CREDENTIALSPublishable key unknown, app not on the key’s allowed-apps list, or the session token is missing, invalid or expired.
- 429RATE_LIMIT_EXCEEDEDThe publishable key went over its hourly cap (1,000 per hour per platform, 300 for web).
curl -X POST "https://api.quickauth.in/v1/sdk/auth/verify" \
-H "X-QuickAuth-Key: pk_live_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"code": "482913"
}'{
"state": "VERIFIED",
"verified": true,
"requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
"message": "Verified successfully",
"idToken": "eyJhbGciOiJIUzM4NCJ9…"
}Record an app launch
/v1/sdk/attribution/launchRecords that your app was opened, with whatever click and referrer data you have, so launches can later be matched to WhatsApp campaign clicks. Our mobile SDKs send it automatically.
Preview: launches are recorded but matching is not switched on yet, so matched is always false today. Safe to call.
AuthPublishable keyX-QuickAuth-Key
Request body
qaClidstringoptionalClick id from a QuickAuth tracked link (also accepted asqa_clid).installReferrerobjectoptionalAndroid install-referrer key/values.urlParamsobjectoptionalQuery parameters from the deep link that opened the app.fingerprintobject | stringoptionalDevice fingerprint.deviceInfoobjectoptionalDevice details.
Response · 200
matchedbooleancampaignIdstring | nulltemplateIdstring | nullvariantIdstring | null
Errors
- 401INVALID_CLIENT_CREDENTIALSPublishable key unknown, app not on the key’s allowed-apps list, or the session token is missing, invalid or expired.
- 429RATE_LIMIT_EXCEEDEDThe publishable key went over its hourly cap (1,000 per hour per platform, 300 for web).
curl -X POST "https://api.quickauth.in/v1/sdk/attribution/launch" \
-H "X-QuickAuth-Key: pk_live_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"qaClid": "c_7Hq2LmX",
"urlParams": {
"utm_source": "whatsapp"
}
}'{
"matched": false,
"campaignId": null,
"templateId": null,
"variantId": null
}Record a conversion
/v1/sdk/attribution/conversionRecords an in-app event such as a purchase. Preview: accepted and logged; conversion reporting is not live yet. Returns 200 with an empty body.
AuthPublishable keyX-QuickAuth-Key
Request body
eventstringrequiredEvent name, e.g.purchase.valuenumberoptionalOrder value.currencystringoptionalISO code, e.g.INR.qaClidstringoptionalClick id, if you have it.metadataobjectoptionalAnything else you want stored with the event.
Errors
- 400INVALID_REQUEST
event: must not be blank. - 401INVALID_CLIENT_CREDENTIALSPublishable key unknown, app not on the key’s allowed-apps list, or the session token is missing, invalid or expired.
- 429RATE_LIMIT_EXCEEDEDThe publishable key went over its hourly cap (1,000 per hour per platform, 300 for web).
curl -X POST "https://api.quickauth.in/v1/sdk/attribution/conversion" \
-H "X-QuickAuth-Key: pk_live_YOUR_PUBLISHABLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "purchase",
"value": 1499,
"currency": "INR"
}'