Skip to Content
SDKGetting StartedAuthorization

Authorization

Before the SDK can start a session, it needs to know who the user is. There are two ways to identify a user to the SDK — a JWT minted by your backend (recommended) or a static API key. Both resolve to a Zing session; the SDK keeps that session valid for you and asks for fresh credentials only when it needs them.

Whichever method you use, every session is bound to one value: partner_user_id, your own identifier for the user. It is what maps your user to a Zing user, and it is the only user key that appears in the Pull API and in webhooks. Read Identity mapping before you wire up either method — an integration that never supplies this value works at login and then leaves you with no way to match Zing data back to your members.

Choosing a method

JWT (recommended)API key
Who issues the credentialYour backend, per userZing, once, at onboarding
Where partner_user_id comes fromThe token’s sub claimThe partnerUserId argument you pass to login
ExpiresYes — short-lived, minted on demandNo — static until you revoke it
Good forProduction integrationsLocal testing and prototyping

Use JWT authentication whenever your backend can sign tokens. The API key is simpler to wire up, but a static, non-expiring secret is inherently weaker: anyone who obtains it can identify as any of your users until you revoke it. Treat it as a fallback, not a long-term production choice — and if you do ship it, always pass partnerUserId.

Identity mapping: partner_user_id

partner_user_id is your identifier for a user, as seen by Zing. Zing stores it as the link between your user and the Zing user that holds their coaching profile, workouts, tests, and body composition results.

The link is created on the user’s first successful login and reused on every login after that:

  1. The SDK sends your credential plus the user’s partner_user_id to Zing.
  2. Zing verifies the credential and resolves the pair (your partner account, partner_user_id).
  3. If that pair is new, Zing creates a Zing user for it. If it has been seen before, Zing returns the same Zing user — provisioning is idempotent, so repeated logins never fork the profile.

partner_user_id is scoped to your partner account, not global: two partners can use the same string for two unrelated users without collision. Within your account the pair is unique — one partner_user_id maps to exactly one Zing user, permanently.

Where the value comes from

Login methodValue used as partner_user_idNotes
JWT (ExternalToken)The sub claim of the JWTRead from the verified token. Nothing else in the token identifies the user.
API key (ApiKey)The partnerUserId argumentOptional in the SDK signature, but effectively required in production — see below.

The SDK reports the resolved value back to you: it is the userId carried by the logged-in auth state (partnerUserID on iOS). Log it alongside your own member id when a session starts — that record is what support requests are resolved with.

Where you will see it again

Zing does not expose its internal user UUID to partners. partner_user_id is the only user key in the server-to-server surface:

  • Pull API pathsGET /users, GET /users/{partner_user_id}/workouts, GET /users/{partner_user_id}/tests, GET /users/{partner_user_id}/body_composition.
  • Webhook envelope — every user-scoped event carries partner_user_id, and that is how you route an event to the right member.
  • Delivery auditGET /webhooks/deliveries?partner_user_id=....

Why it matters

  • It is the join key. Without it, Zing data cannot be attributed to a member in your system. There is no lookup by email, device, or any other attribute.
  • If you omit it in API key mode, the SDK generates a random UUID for that login. The session works, the user gets a working Zing profile, and every event and Pull API record for them is filed under a UUID your systems have never seen. You cannot map it back later.
  • A generated id is not stable. It is created per login, not per person: after a logout, a reinstall, or on a second device, the same person logs in under a new id and starts from an empty profile. Their history stays behind on the old id.

Choosing the value

  • Stable and immutable — the same person must always produce the same string, across devices, reinstalls, and app updates. Your internal primary key is usually the right choice.
  • Opaque — do not use an email address, phone number, or anything else that identifies the person directly or that they can change.
  • Unique within your account — never reuse a value for a different person.
  • A string of up to 255 characters. Any format is accepted; a UUID or your own id (for example acme-user-48213) both work.

JWT authentication

Your backend mints a JWT per user and hands it to your app, which passes it to the SDK’s login. Zing verifies the signature against your published keys and reads two claims to identify the user — nothing else in the token is trusted.

Publishing your JWKS

Zing derives your JWKS location directly from the token’s iss claim: {iss}/.well-known/jwks.json (no trailing slash). For verification to succeed and stay fast, that endpoint must be:

  • Reachable over HTTPS, publicly, with no auth in front of it.
  • Low-latency and highly available — a slow or flaky JWKS endpoint slows down or breaks logins for your users.
  • RSA keys only, in standard JWK format (kty: "RSA", n, e). Supported signing algorithms are RS256, RS384, and RS512.
  • Keyed by kid. Give every key a kid so you can rotate keys without downtime — publish the new key under a new kid before signing with it, and only drop the old key once tokens signed with it have expired.

Example response

GET https://api.acme-fitness.com/.well-known/jwks.json

{ "keys": [ { "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "2026-08-01", "n": "39tx62u2Rc_9VGg074XT-ymWm9xNLTv4lwoDZd0l9IM2TsDn_eeGSYXhsE-a_8_9k0WAEJdWKzQnx1sG6au3369evh9eTUXygNYY_zr7rh5cn1HehhG6shR_wi5Aa7VeZPPhKMZPV0f5DSJbaS2M-K1luMdCu6d3vITX0cH1yPL9gpmOIZfFxG8cgTn37yFmlWeLPdbw3Rm4nfVV5AUPnnijkXfeXkQQM3ASP_SexcEZDKOlv0hVghv_I9ycPbRCyno5Vm-ZhY2mCR7DSNpUnCK_oKSLfgjfZ0AN9BwVdrfhbXnejK10OjZuH0lIoefabXQH6gnfv2IlNMEJHtO58w", "e": "AQAB" } ] }

kty, kid, n, and e are all read — kid is matched against the token’s header to select the right key. use and alg are optional and only for clarity. List more than one key while rotating.

Required claims

ClaimRequiredPurpose
issYesMust match the issuer registered with Zing at onboarding, and resolve to your JWKS as above. It is also how Zing resolves which partner account the user belongs to.
subYesYour external ID for the user — this value becomes the user’s partner_user_id. It is the only field Zing uses to identify who the user is, and it’s scoped to your partner account.
expYesTokens without an expiry, or with one in the past, are rejected.

We don’t enforce a maximum lifetime beyond checking that exp hasn’t passed — but a short-lived token is safer, since it’s a live credential for one of your users. If you mint tokens yourself, aim for around 15 minutes and issue a fresh one per login rather than reusing a long-lived one. If you’re using an IdP such as Auth0, this is your API’s configured token lifetime rather than something you set per request — check that setting, since IdP defaults are often much longer.

Keep sub stable for the lifetime of the person’s account. If your IdP rotates its subject identifier — some do on email change or account re-creation — map your own immutable member id into sub yourself rather than passing the IdP’s value straight through.

Example payload

{ "iss": "https://api.acme-fitness.com", "sub": "acme-user-48213", "iat": 1755699300, "exp": 1755700200 }

iat is optional and shown here for completeness; only iss, sub, and exp are read. The header must carry a kid matching a key published in your JWKS (omit it only if your JWKS publishes a single key):

{ "alg": "RS256", "kid": "2026-08-01" }

With this token, the user’s partner_user_id is acme-user-48213 — that is the value you will pass to the Pull API and receive in webhooks.

API key authentication

Use the API key issued to you by Zing, together with a stable partnerUserId for the user:

  • Always pass partnerUserId in production. It becomes the user’s partner_user_id, exactly as sub does in the JWT flow.
  • If you omit it, the SDK generates a random UUID for that login. The session works, but the resulting user is invisible to your systems and is not restored on the next login — see Why it matters.

Because the key itself doesn’t identify a specific user and doesn’t expire, keep it out of anything a user could extract from your app, and rotate it with your Zing contact if you suspect it’s been exposed. Use a key issued for mobile use here, and a separate one for your backend’s Pull API access.

How it fits together

The SDK runs embedded inside your already-authenticated app — login() starts a session for the SDK itself, not a separate sign-in for your app. The last step in each diagram is that call returning once the SDK’s own session is established.

JWT flow:

Your backend Your app Zing SDK Zing backend | | | | |-- JWT ---------------->| | | | sub = partner_user_id| | | | |-- login(jwtToken) --->| | | | |-- credential ------->| | | | |-- verify signature | | | | against your JWKS | | | |-- read sub as | | | | partner_user_id | | | |-- find or create the | | | | Zing user for | | | | (partner, | | | | partner_user_id) | | |<-- session ----------| | |<-- login() returns ---| | | | partner_user_id | |

API key flow:

Your app Zing SDK Zing backend | | | |-- login(apiKey, ------>| | | partnerUserId) | | | |-- credential + ------>| | | partner_user_id |-- validate the API key | | |-- find or create the Zing | | | user for (partner, | | | partner_user_id) | |<-- session -----------| |<-- login() returns ----| | | partner_user_id | |

If you omit partnerUserId in the second flow, the SDK substitutes a freshly generated UUID at this step, and that UUID becomes the user’s partner_user_id.

Where the same value shows up server-side:

Your systems Zing member id ----- you choose the value ----> partner_user_id | | | (partner, partner_user_id) | one Zing user | | |<---- webhook: partner_user_id -------------| |<---- GET /users/{partner_user_id}/... -----|

The SDK keeps the resulting session alive on its own — you don’t need to poll or manage refresh yourself. If a session can no longer be refreshed (e.g. your JWT signer or API key changed), the SDK surfaces a critical error so you can prompt the user to log in again with a fresh credential.

Platform implementation

The call shape differs slightly per platform. See the login examples and error handling for your platform:

For the server side of the same identity — authenticating your backend and validating webhook signatures — see the API overview.

Coordinate your issuer (iss) registration and API keys with your Zing integration contact before going live.

Last updated on