EXPERIMENTAL WIP ONE WHATSAPP CONNECTION NO SLA

Secure quickstart

This path uses the published server verifier with the hosted browser module. It never trusts browser-decoded claims and never uses the Wamen token as the application session.

Before starting

  • Use HTTPS in production. HTTP callbacks are accepted only for localhost and loopback development.
  • Choose the exact application origin. The expected audience is origin:<scheme>://<host>[:port].
  • Install @wamenjs/server on the backend. Use @wamenjs/auth in bundled browser apps or the hosted module for server-rendered and no-build apps.

1. Verify Wamen identity tokens on the backend

Install @wamenjs/server, then create one verifier for the Wamen issuer and your exact application origin.

Install the Wamen server verifier
npm install @wamenjs/server
verify-wamen.ts
import { createWamenVerifier } from "@wamenjs/server";

const APP_ORIGIN = "https://app.example";

const verifier = createWamenVerifier({
  issuer: "https://wamen.link",
  audience: "origin:" + new URL(APP_ORIGIN).origin,
});

export async function verifyWamenIdentity(idToken: string) {
  if (typeof idToken !== "string" || idToken.length === 0 || idToken.length > 16_384) {
    throw new Error("Invalid Wamen identity token");
  }
  return verifier.verify(idToken);
}

2. Exchange the verified identity for your app session

This working Hono example uses an in-memory session map for a local quickstart. Before production, use your application's durable session store without changing the Wamen verification boundary.

Install Hono when your app does not already use it
npm install hono
server.ts
import { randomUUID } from "node:crypto";
import { Hono } from "hono";
import { setCookie } from "hono/cookie";
import { verifyWamenIdentity } from "./verify-wamen.js";

const app = new Hono();
const sessions = new Map<string, { subject: string; expiresAt: number }>();

app.post("/api/wamen/session", async (c) => {
  if (!c.req.header("content-type")?.startsWith("application/json")) {
    return c.json({ error: "expected_json" }, 415);
  }

  const body = await c.req.json<unknown>().catch(() => null);
  if (
    typeof body !== "object" ||
    body === null ||
    !("idToken" in body) ||
    typeof body.idToken !== "string"
  ) {
    return c.json({ error: "invalid_identity_token" }, 400);
  }

  try {
    const identity = await verifyWamenIdentity(body.idToken);
    const sessionId = randomUUID();
    sessions.set(sessionId, {
      subject: identity.subject,
      expiresAt: Date.now() + 8 * 60 * 60 * 1000,
    });
    setCookie(c, "__Host-app_session", sessionId, {
      httpOnly: true,
      secure: true,
      sameSite: "Lax",
      path: "/",
      maxAge: 8 * 60 * 60,
    });
    return c.body(null, 204);
  } catch {
    return c.json({ error: "identity_verification_failed" }, 401);
  }
});

3. Add the hosted browser flow

Register the completion listener before loading the module. Call preventDefault() synchronously so Wamen does not navigate before your backend session request finishes.

Application page
<script>
  async function finishWamenSignIn(event) {
    event.preventDefault();
    const { identity, returnTo } = event.detail;

    try {
      const response = await fetch("/api/wamen/session", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ idToken: identity.idToken }),
      });
      if (!response.ok) throw new Error("The app backend rejected the identity");
      location.replace(returnTo);
    } catch (error) {
      console.error("Wamen sign-in failed", error);
    } finally {
      window.Wamen?.clearIdentity();
    }
  }

  window.addEventListener(
    "wamen:authentication-complete",
    (event) => void finishWamenSignIn(event),
  );
</script>
<script
  type="module"
  src="https://wamen.link/wamen.js"
  data-wamen-redirect-uri="https://app.example/callback"
></script>

<a
  data-wamen-authorize
  data-wamen-return-to="/account"
  href="https://wamen.link/authorize"
>
  Continue with WhatsApp
</a>

Bundled browser applications can install @wamenjs/auth instead. It exposes the same sign-in, callback, identity, and cleanup operations as the hosted module.

Install the Wamen browser client
npm install @wamenjs/auth

4. Test the complete boundary

  1. Start from a clean browser tab and select Continue with WhatsApp.
  2. Confirm the authorization URL contains a random state, an S256 code challenge, and the exact callback URI.
  3. Send the unedited WhatsApp message and return to the same browser tab.
  4. Confirm the backend returns 204, the Wamen browser identity is cleared, and the app session cookie is Secure, HttpOnly, SameSite=Lax, and scoped to /.
  5. Confirm an expired token, wrong audience, wrong issuer, and backend rejection do not create a session.

Prompt for an AI coding agent

Paste this into the agent working in your application repository. It directs the agent to the machine-readable Wamen context and prevents the common insecure shortcuts.

Copy this complete prompt
Integrate Wamen passwordless sign-in into this application.

Authoritative context:
- Read https://wamen.link/llms.txt first.
- Read https://wamen.link/llms-full.txt for the complete current contract.
- Use issuer https://wamen.link exactly.
- Do not invent Wamen APIs, parameters, scopes, claims, packages, or session endpoints.

Current release constraints:
- @wamenjs/auth@0.1.0 and @wamenjs/server@0.1.0 are published on npm.
- Use @wamenjs/auth for bundled browser applications or the hosted module at https://wamen.link/wamen.js for server-rendered and no-build applications.
- Use @wamenjs/server for backend identity verification. The direct jose verifier at https://wamen.link/docs/server-verification remains the framework-independent reference.
- Wamen is authorization-code-and-PKCE-shaped, but it does not claim full OAuth or OpenID Connect compliance.
- Supported scopes are openid and optional phone. Phone requires fresh consent for every login.

Required implementation:
1. Inspect the existing framework, routing, session, cookie, and test conventions before editing.
2. Add a same-origin callback URL. Public callbacks must use HTTPS; HTTP is allowed only for localhost or loopback.
3. Reuse the application's package and framework conventions. For a bundled browser app, install @wamenjs/auth. For a server-rendered or no-build app, register the wamen:authentication-complete listener before loading wamen.js.
4. Stop automatic callback navigation while the app session is created. With @wamenjs/auth, call handleCallback({ navigate: false }); with the hosted module, call event.preventDefault() synchronously. Send only identity.idToken to the app backend, await the response, clear the browser identity in a finally block, then navigate to the validated returnTo only after the backend session succeeds.
5. On the backend, install @wamenjs/server and verify the token with createWamenVerifier({ issuer, audience }). Treat ES256, kid, issuer, exact audience, subject format, iat, exp, the exact 600-second lifetime, jti, amr=["whatsapp"], and the optional phone pair as enforced package invariants.
6. Derive the audience as origin: plus the normalized application origin, including a non-default port when present.
7. Find or create the app user by the verified pairwise subject. Never use a browser-decoded claim or phone number as the stable identity key.
8. Create the application's own opaque session and set a Secure, HttpOnly, SameSite=Lax cookie. Do not use the Wamen identity token as a durable app session.
9. Keep returnTo same-origin. Do not add open redirects.
10. Add behavior tests for successful sign-in, wrong state, backend rejection, wrong issuer or audience, expired token, optional phone, logout, and cookie attributes.

Do not:
- trust JWT claims in browser code;
- log tokens, authorization codes, phone numbers, message bodies, or raw callback URLs;
- request phone unless the product needs it and explains why;
- add access tokens, refresh tokens, remembered Wamen sessions, email, name, or profile claims;
- claim official Meta or WhatsApp affiliation.

Finish by running the project's formatter, type checker, targeted tests, and a browser smoke test of the complete callback-to-session path. Report exact files changed and verification evidence.