Skip to main content
Back to blog

Send Your First Verify OTP With the Devotel Node SDK: From Signup to a Verified Code

A new user walks from signup to a verified OTP in five steps. This post names the exact dashboard route for the API key, shows the two-method pair verify.send / verify.check without boilerplate, and lists the tenant-owned controls an operator enables before shipping to users.

Orbit Editorial Team

A first Verify OTP takes two calls on the Node SDK: orbit.verify.send(to, channel) to issue the one-time code, and orbit.verify.check({ verification_id, code }) to accept or reject whatever the end user typed. That pair covers signup verification, password recovery, device possession at checkout, and most two-step flows an application needs. This post walks from org creation to that first verified pair, so the reader finishes with a code that passed against a real response shape — and can move into production without second-guessing the tutorial.

1. Signup path

Open the signup page and create an organization — work email, organization name, password. The dashboard opens in the same session; the organization carries its own isolated tenant namespace, so nothing downstream requires a trial-approval hop. A sandbox API key (prefix dv_test_sk_) is generated on first run: simulated carriers, no wallet deduct, no live traffic. Swap in a live key (prefix dv_live_sk_) when a real carrier leg is needed — the code path does not change.

2. The dashboard route to the API key

Dashboard → SettingsAPI keys at /settings/api-keys. The New API key form takes a label (e.g. verify-onboarding) and the verify:write scope. Copy the key once — it is shown at creation and hashed thereafter — and keep it in the environment under ORBIT_API_KEY. The same environment variable is the only thing the SDK client needs at construction; no OAuth flow, no session cookie. (The older [Developer → API Keys](https://orbit.devotel.io/developer/api-keys) path redirects to this same settings page, so either route works.)

3. The one Node SDK call pair

Install the SDK — the package the SDK index page enumerates — then make one send / one check. The send returns a verification id to persist; the check round-trips the typed code against that id.

import { Devotel } from '@devotel-orbit/node';

const orbit = new Devotel({ apiKey: process.env.ORBIT_API_KEY });

// Send: issue the OTP to a phone number or email.
const send = await orbit.verify.send('+14155552671', 'sms');
console.log(send.data.request_id);   // 'vfy_…' — the verification id to persist

// Check: match the code the end user typed.
const check = await orbit.verify.check({
  verification_id: send.data.request_id,
  code: '784215',                    // whatever the UI collected
});
console.log(check.data.status);      // 'approved' when the code matches

The send.data.request_id is what the check expects — the canonical { verification_id, code } shape. The legacy positional (to, code) form still works on older 0.1.x code; new code should carry the canonical shape, which the server treats as authoritative. Per-channel behavior spans sms, whatsapp, email, voice, viber, rcs, flashcall, and Silent Network Authentication (sna) with a device-bound token supplied as deviceToken. The full request/response shape and the fallback-chain composition sit behind the Verify API reference and the fallback chains guide.

4. Tenant-owned controls before production

Two controls an operator typically enables before shipping OTP to users: a sender-identity registration record on the from-number — TCR-vetted 10DLC in the US, sender-ID registration elsewhere — and, where US traffic is patterned, the 10DLC campaign route attached to the vetted campaign. Both are deterministic operator-set flags on the tenant. Verify's OTP enforcement never imposes a global gate; everything is per-tenant, per the tenant-scoped gates in the send-gates reference.

5. Where to go from here

Production-ready adds three habits. Keep the key in the environment (never in source). Call orbit.verify.resend(verificationId) when the recipient reports no code, inside the per-verification cooldown. When an operator wants to read why a verification failed, call orbit.verify.getDetail(verificationId) — the consolidated verification row, per-channel attempt summary, full fallback execution timeline, and the masked code-attempt log (****<last4>) — the same payload the dashboard verification drawer renders. The Verify overview in the docs and the quickstart anchor the same flow for curl.

Frequently asked questions

What does channel default to if I omit it?

sms — the per-channel fallbacks across sms, whatsapp, email, voice, viber, rcs, flashcall, and sna sit behind an explicit channel argument, per the fallback chains guide.

How do I resend without generating a new verification?

orbit.verify.resend(verificationId) on the same verification id. The server returns the channel it re-sent on plus the current expiry; a code no longer pending returns 409, an expired one returns 410, not a duplicate dispatch.

Can I read why a verification failed?

orbit.verify.getDetail(verificationId) returns what the dashboard drawer renders: the verification row, per-channel attempt summary, full fallback execution timeline, and a code-attempt log where each submitted code is masked to ****<last4> — never the raw OTP on the wire.

What do I need to ship OTP over SMS in the US?

Two tenant-set controls: a TCR-vetted 10DLC campaign attached to the from-number, plus the sender-identity registration record. Without the campaign, US carriers reject the patterned traffic at the destination, not at Orbit — a per-tenant gate, per the 10DLC registration guide.

Should new code use the positional check(to, code) form?

No — keep the positional form only for back-compat with 0.1.x code. The canonical { verification_id, code } shape is the one SDK responses return, and the server treats the verification id as authoritative.

How does this differ from MFA factors like TOTP or passkeys?

OTP is a delivery channel: the code transits over a carrier. TOTP, push, passkey, and backup-code factors are possession-of-secret flows with their own enroll → verify → revoke lifecycle, documented in the MFA factor suite guide. Factor channels reject a /verify/send attempt with a typed 422/400, not a silent 503 — the wrong channel name fails loudly, not silently.

Send Your First Verify OTP With the Devotel Node SDK: From Signup to a Verified Code — Orbit by Devotel