Quickstart
Pick the integration that fits your team. You can switch later — both use the same keys and dashboard.
Drop-in widget
Embed a secure, pre-built UI. We handle input, code entry, resend and lockout.
Go to widget guide →REST API
Build your own UI and call us from your backend. Two endpoints, signed result token.
Go to API guide →Authentication
All API requests are authenticated with a Bearer token. Your secret key (sk_live_…) must stay on your backend — never ship it to the browser or mobile app. The widget uses a public key (pk_live_…).
# Base URL https://api.doneotp.com # Header on every request Authorization: Bearer sk_live_xxx
Managed widget
The fastest path. Add a container, load the script, and handle the result. We run the entire flow: phone input, country codes, OTP entry, resend timer and lockout.
<div id="doneotp"></div>
<script src="https://cdn.doneotp.com/widget.js"></script> <script> DoneOTP.init({ apiKey: "pk_live_xxx", // public key elementId: "doneotp", theme: { brand: "#10B981" }, // match your UI onVerified: (res) => { // send the token to your backend to confirm verifyOnServer(res.token); }, onFailed: (err) => console.warn(err) }); </script>
/v1/verify/validate-token (see below) so you never trust the browser.REST API
Build your own UI and call two endpoints from your server. Example: send a code, then check it.
# 1 · Send a code curl https://api.doneotp.com/v1/verify/send \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+14155552671" }' # 2 · Check the code curl https://api.doneotp.com/v1/verify/check \ -H "Authorization: Bearer sk_live_xxx" \ -d '{ "phone_number": "+14155552671", "code": "493812" }'
Endpoints
Generates a one-time code and sends it via SMS. Returns immediately with pending.
// 200 OK { "status": "pending", "expires_in": 300 }
Validates the user's code. On success returns a signed, single-use token.
// 200 OK { "status": "verified", "token": "tok_xxx" }
For the widget (Managed mode): your backend confirms the token is genuine and unused. Not needed in Direct mode — there your backend already gets the result.
// 200 OK { "valid": true, "phone_number": "+90532****2719" }
Status codes
| Code | Meaning | When |
|---|---|---|
| 200 | OK | Code sent / verified |
| 400 | Bad request | Wrong code, attempts exhausted, or invalid token |
| 401 | Unauthorized | Missing or invalid API key |
| 402 | Payment required | Insufficient credit balance |
| 403 | Forbidden | IP or number blocklisted / not in allowlist |
| 404 | Not found | No active verification or code expired |
| 429 | Too many requests | Rate limit exceeded |
Security best practices
• Keep your sk_live_… key server-side only.
• Add an IP allowlist so the key only works from your servers.
• In Managed mode, always confirm the token via /validate-token — never trust the browser.
• We mask phone & IP at the write layer and never store the OTP in plain text.