Verified Ham for developers

Let your users sign in with a verified amateur radio call sign. Verified Ham is a standard OpenID Connect provider, so any OIDC or OAuth 2 client library works.

How verification works

A user creates an account, enters their call sign, and we look it up in the licensing authority's public data (the FCC in the US, ISED in Canada). We mail a postcard with a one-time code to the address on that licence; entering the code proves the user receives mail there. We re-check every verified call sign against new licence data, and revoke verification when a licence expires or a call sign is reassigned.

Who can sign in to your app

  • You, always, so you can build and test before verifying.
  • Everyone else once you have verified your own call sign, and only if they have verified theirs. Unverified users are shown how to verify, then sent back to your app.

New to the problem? Why typed-in call signs can't be trusted, and what to do about it.

Get started

Step-by-step guides for Discourse, WordPress, Keycloak, Next.js, Node.js, Python and plain PHP: see all guides. Try a real sign-in and see the tokens in the playground, and use the official sign-in button.

Endpoints

Issuer
https://verifiedham.net
Discovery
https://verifiedham.net/.well-known/openid-configuration
Authorize
https://verifiedham.net/authorize
Token
https://verifiedham.net/token
Userinfo
https://verifiedham.net/userinfo
JWKS
https://verifiedham.net/.well-known/jwks.json

Authorization code flow only, with PKCE (S256) required for every client, confidential and public alike. Confidential clients also authenticate with client_secret_basic or client_secret_post.

Scopes and claims

ScopeClaims
openidsub: a stable, opaque account ID. It never changes, even if the user's call sign does.
callsigncallsign, callsign_country (US or CA), callsign_verified, and callsign_verified_at once verified
profilename, given_name, family_name as they appear on the licence
emailemail, email_verified
licenselicense_class, license_expires
offline_accessA refresh token

Check callsign_verified. A user who has started verifying but not finished gets their call sign with "callsign_verified": false. That call sign comes only from the user, and anyone can start verifying any call sign, so treat it as a hint. Don't use it to grant anything that depends on the person actually holding the licence. The name and licence claims are only released once the call sign is verified. A user who hasn't started gets no call sign claims at all.

Key your users by sub, not by call sign: call signs change (vanity calls) and are eventually reissued to other people. The mailing address is never released.

Example

Send the user to:

https://verifiedham.net/authorize?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://app.example.com/callback
  &scope=openid%20callsign
  &state=RANDOM
  &code_challenge=BASE64URL(SHA256(VERIFIER))
  &code_challenge_method=S256

Then exchange the code, sending both the secret and the verifier:

curl https://verifiedham.net/token \
  -u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET \
  -d grant_type=authorization_code \
  -d code=THE_CODE \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=VERIFIER

A public client sends the same request with client_id in the body instead of -u, since it has no secret.

The ID token carries the claims for the scopes the user approved:

{
  "iss": "https://verifiedham.net",
  "sub": "usr_01J9Z5W5Q8XKJ2Y1T0V3N4M6PA",
  "callsign": "W7APK",
  "callsign_country": "US",
  "callsign_verified": true,
  "callsign_verified_at": 1760500000,
  ...
}

Client types

  • Confidential: server-side apps. You get a client ID and secret, and use PKCE.
  • Public: browser, mobile and desktop apps, which can't keep a secret. You get a client ID only, and use PKCE.
  • Client ID Metadata Documents: use the URL of a JSON document you publish as the client_id, with no registration at all. Until you claim it, users see a warning that the app is unverified. To claim one, add "verifiedham_callsign": "YOURCALL" to the document and claim it here.