Lightning Payroll Partner Session Handoff Back to API documentation →
Partner integration guide

Send them in already signed in.

Turn an OAuth access token you already hold for one of your users into a single-use link that signs that exact person into Lightning Payroll, without them meeting our login page. Built on per-user OAuth grants, so the session that opens is theirs, not the account owner's.

1 endpoint 60s ticket lifetime 1 use per ticket scope session.handoff opt-in per partner
Your server holds the user's access token Lightning Payroll API issues a single-use signin_url ticket User's browser top-level navigation Signed in lands on Pays POST /partner/session-handoff redirect signin_url consume ticket, once

Your server never hands the browser a bearer token. It exchanges one for a 60-second, single-use ticket first.

00The problem this solves

A plain link from your app into Lightning Payroll only works while the user happens to have a live browser session with us. Sessions last 30 minutes of inactivity, so in practice a good share of click-throughs land on our login page instead of where you sent them.

The obvious workaround does not work. GET /api/redirect/company/{company_id} authenticates from the Authorization header and nothing else. A top-level browser navigation cannot set a header, and our web app holds its session token in localStorage rather than a cookie, so following that URL as a link returns 401 regardless of whether the user is signed in. It is a deep-link helper for a caller that can set a header, not a sign-in path. See the Company Redirect Guide for what it is actually for.

01Get a token that names the user

Previously an OAuth grant could only ever be completed by the account administrator, and the access token you received always acted as that administrator. On an account with several payroll users, you had one key for the whole business.

You can now run the normal authorization code flow for each individual user. Request openid session.handoff (plus any payroll API scopes your integration actually needs), send them to /api/oauth/authorize, let them approve the handoff permission, and exchange the code at /api/oauth/token as usual.

Scopes are fixed when the grant is authorized. An existing refresh token cannot acquire session.handoff after the fact; send that user through /api/oauth/authorize again with the new scope. Until they do, the handoff endpoint returns 403, and you should fall back to the normal authorization flow.

What comes back changes, not how you ask for it

The access token now carries the identity of whoever completed the flow:

Who consentedadmin_modesubWhat the token can do
Account administratortrueTheir email addressEverything the administrator can do
Payroll userfalseTheir usernameExactly what that user can do, no more

A payroll user's token is restricted the same way their own login is, including which companies they may open. That is the point: you are acting as them, not as the business.

Refresh tokens keep the identity across rotation, so a grant made for a payroll user stays that user's grant for its whole life. If a user is deactivated or removed from the account, their token stops working at the next exchange rather than quietly falling back to administrator access.

Store one token pair per user, not one per business.

02Turn the token into a signed-in browser

When the user clicks through to Lightning Payroll from your app:

1

Exchange

Your server calls POST /api/partner/session-handoff with that user's access token.

2

Receive

You get back a signin_url.

3

Redirect

You redirect the user's browser to it as a top-level navigation.

4

Land

We sign them in and land them on the screen you asked for.

If you have no valid token for the user, or refreshing theirs fails, fall back to /api/oauth/authorize as normal and use the resulting token.

Request

POST/api/partner/session-handoff

Header: Authorization: Bearer <that user's access token>

FieldTypeRequiredNotes
targetstringNopays (default and currently the only supported target)
company_idpositive integerYesCompany whose Pays screen should open. Must be a company the token's user is allowed to access, otherwise the call is refused rather than producing a link that fails on arrival.
POST /api/partner/session-handoff
BASE_URL="https://<your-au-sandbox-api-host>"

curl -sS -X POST "$BASE_URL/api/partner/session-handoff" \
  -H "Authorization: Bearer $USER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target": "pays", "company_id": 123}'

Response

200 OK
{
  "signin_url": "https://<your-au-sandbox-app-host>/auth/partner-signin#ticket=...",
  "expires_in": 60,
  "target_path": "/admin/pays/company/123"
}

Redirect to signin_url immediately. It works once, and it expires after 60 seconds. Do not store it, email it, log it, or put it behind another redirect that might retry. The ticket rides in a URL fragment (#ticket=), never a query string, specifically so it is never written to a server access log.

If the user's session request cannot be honoured, you find out here rather than after they arrive. A company_id the user is not allowed to open is refused at this call, so you never hand somebody a link that fails on landing.

Common errors

StatusMeaning
403The token is not a partner access token, your OAuth client is not enabled, or the token lacks session.handoff
403The token's user is not allowed to access that company
422target is not pays, or company_id is missing or not a positive integer
404No company with that company_id

03Why not just hand the browser the access token

Because it leaks the token. A credential in a URL is written to the user's browser history, sent onward in the Referer header when the page loads anything external, and captured in the access logs of every proxy between you and us. Our access token is also a bearer credential for the whole payroll account behind a 30-day refresh chain, so a copy in a log file is a real incident.

The handoff ticket is the same idea without the exposure: it is single use, it lives for a minute, and it grants nothing beyond the session the token already stood for.

04What this does not do

  • It does not create access. The session is exactly the one the presented token already represented.
  • It does not skip authentication. Your user proved who they were, with a password and a one-time code, when they completed the OAuth flow.
  • It requires explicit OAuth consent for session.handoff; that consent is collected during the normal authorization flow, not during each click-through.
  • It does not extend the session. Once inside, the usual 30-minute idle timeout applies, and the user will need a fresh click-through afterwards.
  • It is not a general SSO endpoint. It only signs in a user we already issued you a token for.

05Security notes

  • Treat each user's tokens the way you would treat their password. One user's token must never be used to mint a link for a different user.
  • The ticket in signin_url is a credential. Redirect to it, do not log it.
  • Sessions created this way are recorded as originating from your integration, so we can answer "who sent this user here" if a customer ever asks.
  • Revoking a refresh token, or deactivating the user in Lightning Payroll, stops future handoffs for that person.

06Integration checklist

1

Run the OAuth flow once per user with session.handoff, not once per business.

2

Store and refresh each user's tokens separately.

3

Call the handoff endpoint from your server, with the clicking user's token.

4

Redirect straight away, and never reuse a link.

5

Fall back to /api/oauth/authorize when you have no working token for someone.

07Related guides

This guide assumes you already run the OAuth flow. These cover the rest of it.