Skip to content

API in Capabilities

WebOTP API

Published

The WebOTP API extends Credential Management so a site can request an SMS one-time code that is bound to its domain. After the user consents, navigator.credentials.get() returns an OTPCredential whose code can fill a verification form (WebOTP API, MDN).

MDN marks WebOTP as limited availability and restricted to secure contexts. Its compatibility data records OTPCredential from Chrome 93 on desktop and Chrome 84 on Android, with support mirrored to Edge, Opera, Samsung Internet, and Meta Quest Browser; it records no support in Firefox, Safari, or Android WebView (WebOTP API, MDN). A field with autocomplete="one-time-code" preserves manual entry and lets Safari offer its separate SMS-code autofill (Verify phone numbers on the web with the WebOTP API, Chrome for Developers).

const credential = await navigator.credentials.get({
otp: { transport: ['sms'] },
signal,
});

The SMS must end with the calling domain and code. The domain has no scheme or port, and the domain-bound line is the last line (WebOTP API, MDN).

Pass an otp option whose transport contains "sms". An optional AbortSignal cancels a pending request. When the request succeeds, the returned OTPCredential exposes the received value through code (WebOTP API, WICG).

A policy can disable the API with the otp-credentials Permissions Policy directive. Its default allowlist is self; a request blocked by policy rejects with SecurityError (WebOTP API, MDN). The request can also reject when its abort signal is triggered, so keep the input and submit button usable.

The first example shows the server-produced SMS shape; the second keeps a manual verification path while requesting the code.

For https://www.example.com, MDN gives this message shape:

Your verification code is 123456.
@www.example.com #123456

The blank line separates the explanatory text from the final domain-bound line.

Fill the code while retaining manual entry

Section titled “Fill the code while retaining manual entry”

This form keeps the standard input available whether or not WebOTP exists. Submitting manually aborts the pending credential request.

<form id="otp-form" action="/verify" method="post">
<label>Verification code <input name="code" autocomplete="one-time-code" inputmode="numeric" required></label>
<button>Verify</button>
</form>
<script>
const form = document.querySelector('#otp-form');
const input = form.elements.code;
if ('OTPCredential' in window) {
const controller = new AbortController();
form.addEventListener('submit', () => controller.abort(), { once: true });
navigator.credentials.get({
otp: { transport: ['sms'] },
signal: controller.signal,
}).then((otp) => {
input.value = otp.code;
form.requestSubmit();
}).catch(() => {
input.focus();
});
} else {
input.focus();
}
</script>

The fallback focuses the same field, so unsupported browsers retain the manual verification path.

Specifications

SpecificationStatus
WebOTP APIWICG draft