Core concepts
Embedded web app
Sign your own users into Firsty's hosted app with a signed JWT.
Instead of building against the API, you can embed Firsty's own web app in your product. Your users get data plans without a second sign-up, because you stay the authentication authority: you assert who the user is with a short-lived signed JWT. Firsty verifies it, provisions the matching account, and runs the session from there.
How it works
- You open the web app at
https://web.firsty.app/<partnerCode>?userToken=<JWT>. - The web app exchanges that token with the Firsty backend.
- Firsty verifies the signature and claims, resolves or creates the user, and signs them in. Session handling is Firsty's from that point.
You never call the Firsty backend yourself. Your side is two things: configuring how Firsty verifies your tokens, and minting a token per user before opening the URL.
Onboarding
| Item | Who provides it | Notes |
|---|---|---|
partnerCode | Firsty | Your identifier. Goes in the embed URL and the token's iss claim. |
| Verification mode | You | JWKS or shared secret. |
| Keys or JWKS URL | You | A public JWKS endpoint, or the shared secret. |
| Signing algorithm | You | One of the supported algorithms below. |
The partnerCode is the same in every environment, but verification config is
provisioned per environment, so keys can differ. Confirm with Firsty which key
belongs to which.
Verifying your tokens
Both modes require TLS 1.2 or higher.
JWKS, recommended
You host a public JSON Web Key Set, for example at
https://your-domain.com/.well-known/jwks.json. You sign with your private
key; Firsty fetches the matching public key to verify. Rotation is entirely
yours to manage: publish a new key and it's live, with no secret to exchange.
Each key's kid must match the kid in the header of tokens signed with it.
Supported: PS256, PS512, ES256, ES512, EdDSA.
Shared secret
Firsty and you exchange a secret over a secure channel, and it both signs and
verifies. Supported: HS256, HS512.
Firsty can hold several secrets at once and accepts a token signed by any of them, so rotation needn't take downtime: add the new secret, move your signer to it, then retire the old one.
The user token
Mint a JWT per signed-in user.
| Claim | Meaning | Constraints |
|---|---|---|
sub | Your identifier for the user | 10 to 128 characters. Letters, digits, - and _ only. |
iss | Issuer | Exactly your partnerCode. |
aud | Audience | Exactly firsty.app. |
iat | Issued at | Seconds since epoch. |
exp | Expiry | Seconds since epoch. Keep it short; 5 minutes is recommended. |
campaign | Optional signup coupon | Max 128 characters, and must name a campaign Firsty has provisioned. See below. |
sub is the identity. Firsty recognises the same user across sessions by it,
and it's how you find them in the partner area, so it has to be stable for a
given user and never reused across two. The token is passed as a URL query
parameter, which is the other reason to keep the lifetime short and generate
it at the moment you open the embed URL. A small clock skew between your
servers and Firsty is tolerated.
Header, signing asymmetrically. Include kid so Firsty can pick the right key:
Payload:
For shared-secret mode the payload is identical, alg is HS256 or HS512,
and no kid is needed.
Opening the web app
Put the partnerCode in the path and the fresh token in userToken:
Load it in a WebView on mobile, or an iframe or new tab on web. Firsty exchanges the token and establishes the session. A user who comes back while that session is still valid needs no new token; otherwise open the URL again with a fresh one.
| Environment | Host |
|---|---|
| Production | https://web.firsty.app |
| Test (UAT) | https://web.test.firsty.app |
Both are provisioned during onboarding, each with its own verification config, so sign with the key that belongs to the host you're opening. Validate the whole flow on test before you point at production.
Signup coupons
Firsty can attach a coupon to a partner user, at signup or on a later login.
You ask for it with the campaign claim, and Firsty reserves a code and
applies it at checkout on the user's behalf. There's no separate API call; the
claim is part of the signed token, so it can't be tampered with in transit.
A campaign has to exist before you can name one. Ask your Firsty representative or get in touch, and Firsty provisions the following per environment:
| Item | Notes |
|---|---|
| Campaign name | The exact string you send in the campaign claim. |
| Discount | Type (free days, fixed amount, percentage) and value. |
| Active period | The coupon only applies while the campaign is running. |
| Code pool | Pregenerated codes. Each user reserves one; an exhausted pool assigns nothing. |
How it behaves:
- Reserved when the user has no coupon yet. First time Firsty sees the
claim for a
(partnerCode, sub)pair, and again on a later login if that user still holds none. A user created before the campaign existed can be given one by sending the claim again. - One coupon per user, across all campaigns. If they already hold one, the request reuses it. Sending a different campaign name does not swap it.
- Applied automatically at checkout, while the campaign is active. The user never types a promo code.
- Active campaigns only. An expired or not-yet-started campaign assigns nothing, even with codes left in the pool.
Accounts
Every partner user gets their own Firsty account, keyed on (partnerCode, sub). First sight of a pair provisions it, and every later token with the same
pair resolves to that same user, so returning users keep their data.
Partner accounts stay separate from native Firsty users and from other partners' users. They are never merged, even when they share a phone number or email address. The embedded experience covers data plans; voice calling isn't part of it.
When it fails
| Situation | Result |
|---|---|
| Signature invalid, token expired, or claims fail validation | Unauthorized. Mint a new, valid token. |
Unknown partnerCode, or config not provisioned for that environment | Unauthorized. Confirm onboarding for the environment you're opening. |
| Too many verification attempts in a short window | Throttled. Back off and retry. |
campaign names an unknown, inaccessible or depleted campaign | Login succeeds, no coupon assigned. |
Claim validation covers the sub format and length, aud being exactly
firsty.app, iss matching your partnerCode, and the token lifetime not
being too long. If tokens are rejected and the claims look right, check that
the signing algorithm and key match what Firsty provisioned for that
environment.
Anything else, get in touch or ask in your shared Slack channel if you have one.