Vanilla JavaScript
Integrate Phonelink phone verification into any web app using plain JavaScript or TypeScript.
Overview
The web client uses a redirect-based flow. Your app redirects the user to Phonelink, they verify their phone number, and Phonelink redirects back with a signed JWT token.
The flow requires two pages in your app:
- Start page — where the user clicks "Verify Phone" and gets redirected
- Callback page — where your app receives the token after verification
Install
npm install phonelink1. Start the verification flow
On the page where users initiate verification, call phonelink.verify:
import { phonelink } from "phonelink/web";
const verifyButton = document.getElementById("verify-btn");
verifyButton.addEventListener("click", () => {
phonelink.verify("your-client-id", "https://myapp.com/auth/callback");
});This does three things:
- Generates a cryptographic nonce (32 random bytes)
- Stores the nonce in
sessionStorageunder the keyphonelink_nonce - Redirects the browser to
https://phone.link/authwith your client ID, callback URL, and nonce as query parameters
| Parameter | Type | Description |
|---|---|---|
clientId | string | Your Phonelink client ID |
callbackUrl | string | URL to redirect back to after verification |
2. Handle the callback
On your callback page, call phonelink.getResult to retrieve the token and nonce:
import { phonelink } from "phonelink/web";
const result = phonelink.getResult();
if (result) {
// Send the token and nonce to your server for verification
const response = await fetch("/api/verify-phone", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: result.token,
nonce: result.nonce,
}),
});
if (response.ok) {
const { phone } = await response.json();
console.log("Verified phone number:", phone);
}
} else {
console.log("No verification result found");
}getResult does the following:
- Reads the
tokenquery parameter from the current URL - Retrieves the nonce from
sessionStorage - Removes the nonce from
sessionStorage(to prevent reuse) - Returns
{ token, nonce }ornullif either is missing
3. Verify on the server
Send the token and nonce to your backend and verify using phonelink/validate. See the Server Verification guide for the full walkthrough.
import { validate } from "phonelink/validate";
const payload = await validate(token, nonce, "your-client-id");
console.log(payload.phone_e164); // "+14155551234"Complete example
Here's a complete HTML example with both pages:
verify.html — the start page:
<!DOCTYPE html>
<html>
<body>
<button id="verify-btn">Verify Phone Number</button>
<script type="module">
import { phonelink } from "phonelink/web";
document.getElementById("verify-btn").addEventListener("click", () => {
phonelink.verify("your-client-id", "https://myapp.com/callback.html");
});
</script>
</body>
</html>callback.html — the callback page:
<!DOCTYPE html>
<html>
<body>
<p id="status">Verifying...</p>
<script type="module">
import { phonelink } from "phonelink/web";
const result = phonelink.getResult();
if (result) {
const response = await fetch("/api/verify-phone", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: result.token, nonce: result.nonce }),
});
if (response.ok) {
document.getElementById("status").textContent = "Phone verified!";
} else {
document.getElementById("status").textContent = "Verification failed.";
}
} else {
document.getElementById("status").textContent = "No token found.";
}
</script>
</body>
</html>Important notes
- Same browser session required — The nonce is stored in
sessionStorage, which is scoped to the browser tab. The user must complete verification in the same tab they started from. - Single-use nonce —
getResultremoves the nonce from storage after reading it. Calling it twice will returnnullthe second time. - Always verify server-side — The web client never verifies the JWT. Always send the raw token to your server for cryptographic validation.