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" }'
API playground
Edit the phone number and OTP code below to generate a ready-to-run cURL command. Calls are not executed — this is a sandbox preview.
# Send OTP
curl https://api.doneotp.com/v1/verify/send \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "phone_number": "+14155552671" }'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 |
SDKs
Official client libraries for popular stacks. REST API works with any HTTP client today — SDKs ship soon.
Node.js
JavaScript / TypeScript
Coming soonPython
Python 3.8+
Coming soonGo
Go 1.21+
Coming soonPHP
PHP 8.1+
Coming soon.NET
C# / .NET 8
Coming soonWebhooks
Receive server-side events when verifications complete or fail. Configure webhook URLs in the dashboard — we sign each payload with your secret so you can verify authenticity.
POST https://your-app.com/webhooks/doneotp
Content-Type: application/json
X-DoneOTP-Signature: sha256=...
{
"event": "verification.completed",
"verification_id": "ver_xxx",
"phone_number": "+90532****2719",
"token": "tok_xxx"
}| Event | Description |
|---|---|
verification.completed | OTP verified successfully — includes signed token |
verification.failed | Max attempts reached or code expired |
verification.send_failed | SMS could not be delivered — credit refunded |
Rate limits
Default limits protect against abuse and SMS pumping. Volume plans can request higher thresholds via sales@doneotp.com.
| Scope | Default limit | Notes |
|---|---|---|
| Per API key | 100 req/min (default) | Burst allowed; HTTP 429 when exceeded |
| Per phone number | 5 send attempts / hour | Prevents SMS pumping abuse |
| Per IP (widget) | 20 sessions / hour | Configurable in dashboard |
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.
Developer FAQ
How do I authenticate with the DoneOTP API?
All API requests use a Bearer token. Keep your secret key (sk_live_…) on your backend only. The drop-in widget uses a public key (pk_live_…) safe for the browser.
Should I use the widget or REST API?
Use the drop-in widget for the fastest path — we handle the entire verification UI. Use the REST API when you need full control over the user experience and already have your own phone input flow.
What are the rate limits?
Rate limits protect against abuse and SMS pumping. Limits apply per API key and per phone number. Exceeding limits returns HTTP 429. Contact sales for higher limits on volume plans.
How do I validate tokens in Managed mode?
After the widget verifies a user, call POST /v1/verify/validate-token from your backend with the signed token. Never trust verification results from the browser alone.
What happens when verification fails?
Failed SMS sends are not charged — credits are refunded automatically. Invalid or exhausted attempts return HTTP 400. Expired codes return HTTP 404.