OTP API documentation

OTP API Documentation & Integration Guide

Two ways to build: a drop-in widget for the fastest path, or a two-endpoint REST API for full control. No telecom expertise required.

Quickstart

Pick the integration that fits your team. You can switch later — both use the same keys and dashboard.

Managed · Widget

Drop-in widget

Embed a secure, pre-built UI. We handle input, code entry, resend and lockout.

Go to widget guide →
Direct · REST API

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
🔐 Lock keys to your servers with an IP allowlist in the dashboard — even a leaked key won't work from another machine.

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.

1
Add a container
<div id="doneotp"></div>
2
Load & initialize
<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>
3
Confirm the token on your backend — call /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" }'
Sandbox preview — replace sk_live_xxx with your secret key.

Endpoints

POST /v1/verify/send

Generates a one-time code and sends it via SMS. Returns immediately with pending.

// 200 OK
{ "status": "pending", "expires_in": 300 }
POST /v1/verify/check

Validates the user's code. On success returns a signed, single-use token.

// 200 OK
{ "status": "verified", "token": "tok_xxx" }
POST /v1/verify/validate-token

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

CodeMeaningWhen
200OKCode sent / verified
400Bad requestWrong code, attempts exhausted, or invalid token
401UnauthorizedMissing or invalid API key
402Payment requiredInsufficient credit balance
403ForbiddenIP or number blocklisted / not in allowlist
404Not foundNo active verification or code expired
429Too many requestsRate 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 soon

Python

Python 3.8+

Coming soon

Go

Go 1.21+

Coming soon

PHP

PHP 8.1+

Coming soon

.NET

C# / .NET 8

Coming soon

Webhooks

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"
}
EventDescription
verification.completedOTP verified successfully — includes signed token
verification.failedMax attempts reached or code expired
verification.send_failedSMS 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.

ScopeDefault limitNotes
Per API key100 req/min (default)Burst allowed; HTTP 429 when exceeded
Per phone number5 send attempts / hourPrevents SMS pumping abuse
Per IP (widget)20 sessions / hourConfigurable 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.