threshold1

BYOAM (bring-your-own-auth-method)

BYOAM lets your own existing auth system — your own SMS OTP, your own password check, your own SSO — vouch for a login without threshold1 ever seeing the underlying credential. You register a method once in the dashboard; from then on, a completed login through your own system can be turned into a real threshold1 session.

There are two integration patterns — a push model and a pull model. Pick whichever fits how your own backend is set up.

Pattern A — confirm_callback (push)

Fully working today. Your own backend calls threshold1 directly, after your own auth method has already succeeded. threshold1 validates the call and issues a real session — same as any other successful login.

1. Register the method

In the dashboard, go to your project → BYOAMRegister Method. You'll choose:

  • Method Name — any name unique within the project (e.g. acme_sms). This is what you'll send back in the method field below.
  • Pattern — choose Push (confirm_callback).
  • Assurance Level — a self-attested 0–100 strength score for this method. threshold1 can't independently verify a method it never sees the internals of; this is stored for future risk-weighting but is not enforced or checked today.

On submit, a secret is generated and shown exactly once — copy it immediately. This is what you'll use to sign every request in step 3. There's no webhook URL to configure for this pattern — your backend calls threshold1, not the other way around.

2. Verify your own user, your own way

Run your own existing auth method (your own SMS code, your own password check, whatever it is) however you already do it today. threshold1 is not involved in this step and never sees the credential.

3. Call the confirm endpoint

Once your own check succeeds, your backend calls:

POST /api/v1/external-auth/confirm

with:

  • Authorization: Bearer <your API key> — same as every other /api/v1/* call.
  • X-Threshold1-Signature: <hex HMAC-SHA256> — the raw request body, signed with the BYOAM registration's own secret from step 1. This is a separate credential from the API key — it proves you specifically hold this registration's secret, not just any valid key for the project.
import { createHmac } from "crypto";

const rawBody = JSON.stringify({
  externalUserId: "your_user_123",
  method: "acme_sms",
  verifiedAt: new Date().toISOString(),
  // email is optional — see the note below
});

const signature = createHmac("sha256", BYOAM_SECRET)
  .update(rawBody)
  .digest("hex");

await fetch("https://<your threshold1 host>/api/v1/external-auth/confirm", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "X-Threshold1-Signature": signature,
    "Content-Type": "application/json",
  },
  body: rawBody, // send these exact bytes — see note below
});

Sign and send the exact same bytes. The signature is computed over the raw request body. If you build the signature from one object and then re-serialize it before sending (different key order, different whitespace), the bytes won't match what you signed and the request will fail with INVALID_SIGNATURE — even though the data is identical. Sign the literal string you're about to send, not a re-serialized copy of it.

verifiedAt has a 60-second freshness window. A request is only accepted if verifiedAt is within 60 seconds of when threshold1 receives it — this isn't a general-purpose "log a past event" endpoint, it's a live vouch for something that just happened.

email is optional, with one exception: if this externalUserId has never been seen before, threshold1 needs a real email to create the new user record — pass it the first time. Once the user exists, later confirm calls for the same externalUserId don't need to repeat it.

What you get back

{
  "success": true,
  "token": "eyJ...",
  "userId": "c3588a12-...",
  "externalUserId": "your_user_123",
  "isNewUser": false
}

A real session, a real JWT — from threshold1's side this is a completed login, method: "byoam". Real user.registered / user.login webhooks fire, exactly like any other successful auth.

Pattern B — verify_webhook (pull)

Registrable and dashboard-testable today. Not currently consulted during any real login or registration. Read that precisely before you build against it: you can register a Pattern B method from the dashboard, and the dashboard's own Test button will call your webhook to confirm it's reachable and answering with the right shape. But there is no API endpoint and no real login or registration code path that calls out to your webhook today. Registering a method and passing the dashboard test has no effect whatsoever on real authentication — no genuine end-user login will ever reach your webhook. This is a deliberate, permanent state of this feature, not a "coming soon" — do not register this believing it functions as an enforced step-up or security control, because nothing currently enforces it.

What follows describes the real registration flow and the real contract your webhook needs to satisfy for the dashboard's Test button — useful groundwork if you're building a receiver, just not something a real login currently triggers.

1. Register the method

In the dashboard, go to your project → BYOAMRegister Method. You'll choose:

  • Method Name — any name unique within the project (e.g. acme_sso). This identifies the method on every check.
  • Pattern — choose Pull (verify_webhook).
  • Webhook URL — must be HTTPS. This is the endpoint threshold1 calls out to on every check.
  • Assurance Level — a self-attested 0–100 strength score for this method. threshold1 can't independently verify a method it never sees the internals of; this is stored for future risk-weighting but is not enforced or checked today.

On submit, a secret is generated and shown exactly once — copy it immediately. threshold1 signs every outbound call to your webhook with this secret, so you can verify the request genuinely came from threshold1 before trusting it.

2. Build your webhook receiver

On every check, threshold1 sends your registered webhook_url:

POST <your webhook_url>
Content-Type: application/json
X-Threshold1-Signature: <hex HMAC-SHA256 of the raw body below, keyed by your registration secret>

{
  "external_user_id": "your_user_123",
  "method": "acme_sso"
}

Verify the signature before trusting the request — same construction as Pattern A's inbound check, just in reverse:

import { createHmac, timingSafeEqual } from "crypto";

function isFromThreshold1(rawBody, signatureHex, secret) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHex, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

Respond within 5 seconds with whether external_user_id has completed your own auth method right now:

{ "verified": true }

or { "verified": false } if they haven't — that's a normal, expected answer, not an error.

3. Test it from the dashboard

Back on the BYOAM page, your Pattern B registration has a Test button. Clicking it makes threshold1 call your registered webhook right now, with a synthetic external_user_id (dashboard-test-<timestamp>) that will never match a real user. This proves connectivity, signature handling, and response shape — it does not simulate a real login, and it isn't wired into one.

A { "verified": false } result from the test is expected and is not a failure — a synthetic ID can never legitimately verify. What the test actually tells you is whether the call reached your endpoint and came back in the right shape (reachable / times out / wrong shape), nothing more.

Timeout and failure behavior (of the dashboard test call)

The dashboard's Test call fails fast rather than hanging: 5 seconds, then it gives up. If your endpoint times out, is unreachable, or returns something other than a valid { "verified": boolean } body, the test reports exactly that back to you in the dashboard. This is the same underlying call a future real integration would use — but today, it only ever runs when you click Test.

What's next

  • Core Concepts → External user ID bridging covers how externalUserId resolution works across every method, including BYOAM.
  • Auth methods reference covers the native methods (Passkey, Email OTP, Magic Link, TOTP, Backup Codes) BYOAM sits alongside.