Embedded component
@payrollkit/react embeds PayrollKit's employer setup and payroll workflows in your React application with your configured branding, so employers can run payroll without leaving your product.
See Onboard an employer and Run payroll each period for the complete integration journeys.
Install
@payrollkit/react is the intended package name. The package has not been published; the Developer Preview will provide a tested prerelease version and installation instructions.
npm install @payrollkit/react
Create a session
Before showing PayrollKit, confirm that the signed-in user can access payroll for the Employer, then create an embedded session on your server.
- Create a new session for each browser tab or full page load. If one page mounts more than one PayrollKit component at the same time, create a separate session for each component.
- Pass its
sessionTokendirectly to that page and keep it only in memory. The token is a secret, so never put it in a URL, browser storage, analytics or logs. - Store the session
idon your server so you can revoke it when the user signs out or loses access.
Revoke sessions as follows:
- On sign-out, revoke the current page's session.
- When a user loses access to an Employer, revoke all their active sessions for that Employer.
- When a user is disabled, revoke all their active sessions across every Employer.
Mount PayrollKit
import {
PayrollKit,
PayrollKitError,
type PayrollKitHandle,
} from "@payrollkit/react";
const payrollKit = useRef<PayrollKitHandle>(null);
<PayrollKit
ref={payrollKit}
sessionToken={session.sessionToken}
initialView={{ view: "PAY_RUN", payRunId: "payrun_a3f9c217" }}
/>
Component properties
export interface PayrollKitProps {
sessionToken: string;
initialView?: PayrollKitView;
theme?: PayrollKitTheme;
onReady?: () => void;
onViewChanged?: (event: { view: PayrollKitView }) => void;
onActionCompleted?: (event: PayrollKitActionCompletedEvent) => void;
onSessionExpiring?: (event: PayrollKitSessionExpiringEvent) => void;
onSessionEnded?: (event: PayrollKitSessionEndedEvent) => void;
onError?: (error: PayrollKitError) => void;
}
export interface PayrollKitTheme {
appearance?: "light" | "dark" | "system";
accentColor?: `#${string}`;
}
export type PayrollKitAction =
| "EMPLOYER_UPDATED"
| "PAYE_SCHEME_UPDATED"
| "EMPLOYMENT_UPDATED"
| "PAY_RUN_UPDATED"
| "PAY_RUN_APPROVED"
| "BANK_PAYMENT_FILE_CREATED"
| "ACCOUNTING_EXPORT_CREATED"
| "PENSION_EXPORT_CREATED"
| (string & Record<never, never>);
export interface PayrollKitActionCompletedEvent {
eventId: string;
action: PayrollKitAction;
resource: {
employerId?: string;
employmentId?: string;
payRunId?: string;
bankPaymentFileId?: string;
accountingExportId?: string;
pensionExportId?: string;
[resourceId: `${string}Id`]: string | undefined;
};
}
export interface PayrollKitSessionEndedEvent {
reason: "expired" | "revoked";
}
export interface PayrollKitSessionExpiringEvent {
expiresAt: string;
}
export type PayrollKitErrorCode =
| "component_load_failed"
| "component_not_ready"
| "component_unmounted"
| "invalid_view"
| "navigation_timed_out"
| "network_unavailable"
| "record_not_available"
| "session_already_mounted"
| "session_ended"
| "session_invalid"
| "session_replacement_failed"
| "session_token_changed"
| "unexpected_error"
| "unsupported_component_version"
| (string & Record<never, never>);
export declare class PayrollKitError extends Error {
readonly code: PayrollKitErrorCode;
readonly requestId?: string;
}
export interface PayrollKitHandle {
navigate(view: PayrollKitView): Promise<void>;
replaceSession(sessionToken: string): Promise<void>;
}
| Property | Required | What it does |
|---|---|---|
sessionToken | Yes | Supplies the session when the component is mounted. Use replaceSession() before expiry; mount separately when the Employer or user changes. |
initialView | No | Chooses the first view to display when the component mounts. PayrollKit opens HOME when omitted. |
theme | No | Adjusts the component's appearance. You can update it while the component is mounted. |
onReady | No | Triggers once each time the component is mounted, after the session and first view have loaded. |
onViewChanged | No | Triggers when the current supported view changes. |
onActionCompleted | No | Triggers when a completed Employer action changes a resource. |
onSessionExpiring | No | Triggers once when the current token has five minutes remaining. |
onSessionEnded | No | Triggers when the session has expired or been revoked. |
onError | No | Triggers on an asynchronous component error that is not returned by a method call. PayrollKit's own error state does not depend on this callback. |
Component lifecycle
When the component first mounts
- Wait for
onReadybefore callingnavigate()orreplaceSession(). Calls made earlier reject withcomponent_not_ready. - If
initialViewis invalid or unavailable, PayrollKit reports the problem throughonError, falls back toHOMEand then callsonReady.
While the component is mounted
- To change views, call
navigate(). ChanginginitialViewafter mounting has no effect. - To renew a session for the same user and Employer, call
replaceSession(). ChangingsessionTokenclears payroll information and callsonErrorwithsession_token_changed. - You can update
themeand callback properties without remounting.
When the component is unmounted
- PayrollKit stops all callbacks and discards unsaved input.
- The session remains valid, and actions PayrollKit has already saved are not undone.
- If you remount within the same page, you may reuse the valid in-memory token. Saved data reloads, but unsaved input is lost.
Views and navigation
PayrollKit sits inside your application, which continues to control the page URL, breadcrumbs and surrounding navigation.
- Set
initialViewto choose which view PayrollKit shows when the component first loads. If you leave it out, PayrollKit opensHOME. - Call
navigate()to change the view after the component has loaded.
Available views
export type PayrollKitView =
| { view: "HOME" }
| { view: "EMPLOYEES" }
| { view: "EMPLOYEE"; employeeId: string }
| { view: "PAY_RUNS" }
| { view: "PAY_RUN"; payRunId: string };
| View | What it shows | Required ID |
|---|---|---|
HOME | Setup progress, recent Pay Runs and the next payroll action | None |
EMPLOYEES | Payroll employees and setup issues | None |
EMPLOYEE | One employee's payroll details | employeeId |
PAY_RUNS | Upcoming and completed Pay Runs | None |
PAY_RUN | One Pay Run, from resolving issues through review, approval and outputs | payRunId |
PayrollKit handles setup, issues, review and outputs within these views. You can link to a Pay Run without needing to track which step the employer is on.
Change the view
Call navigate() with one of the views above:
try {
await payrollKit.current?.navigate({
view: "PAY_RUN",
payRunId: "payrun_a3f9c217",
});
} catch (error) {
if (error instanceof PayrollKitError) {
handlePayrollNavigationError(error);
}
}
navigate() only opens records belonging to the Employer associated with the session. Otherwise, it rejects with record_not_available.
onViewChanged runs when the Employer or navigate() moves PayrollKit to a different public view. It does not run for the initial view. Calling navigate() with the current view and resource ID resolves without emitting another event.
Events
Use component events to refresh your page while PayrollKit is mounted and respond when the session ends:
- Use
onActionCompletedas a prompt to fetch the affected resource after PayrollKit accepts and records an employer action. - Use signed webhooks for server-side notifications that can be recorded independently of the page, including later work such as HMRC filing and Payslip progress.
- Use
onSessionExpiringto replace a token before a long-running session ends. - When
onSessionEndedfires, remove payroll information from the page. Request a new session if it expired. If it was revoked, confirm that the user still has access first.
Add only the callbacks your page needs:
<PayrollKit
sessionToken={session.sessionToken}
onViewChanged={({ view }) => updatePartnerRoute(view)}
onActionCompleted={(event) => refreshPayrollResource(event)}
onSessionExpiring={() => {
void replaceEmbeddedSession().catch(handleSessionReplacementError);
}}
onSessionEnded={({ reason }) => handleSessionEnd(reason)}
onError={(error) => showPayrollError(error)}
/>
Actions reported by PayrollKit
onActionCompleted tells your application what changed and includes the IDs needed to fetch the affected resource.
| When this happens | action value | IDs returned |
|---|---|---|
| Employer updates their payroll details | EMPLOYER_UPDATED | employerId |
| Employer updates their PAYE Scheme details | PAYE_SCHEME_UPDATED | employerId |
| Employer updates an employee's payroll setup | EMPLOYMENT_UPDATED | employmentId |
| Employer resolves a Pay Run issue or creates a Pay Run Input Revision | PAY_RUN_UPDATED | payRunId |
| Employer approves the reviewed calculation | PAY_RUN_APPROVED | payRunId |
| A requested accounting export record is created | ACCOUNTING_EXPORT_CREATED | payRunId, accountingExportId |
| A requested pension export record is created | PENSION_EXPORT_CREATED | payRunId, pensionExportId |
| A requested bank payment file record is created | BANK_PAYMENT_FILE_CREATED | payRunId, bankPaymentFileId |
Accounting, pension and bank payment files are created only when requested. A CREATED event means that the record exists, but the file may still be processing. Fetch the record for its current status.
Callbacks may arrive late, more than once or out of order. They may also be lost when the page closes or the component unmounts. Earlier events are not replayed when PayrollKit mounts again.
Each completed action has a unique eventId. Repeated callbacks for that action use the same ID, so you can identify duplicates. The ID does not indicate order and is not shared with webhook deliveries. Match callbacks and webhooks using the affected resource IDs instead.
PayrollKit may add action values in backwards-compatible releases. Do not treat an unrecognised value as an error. Refresh the resource when your application supports the supplied ID; otherwise ignore that callback and use webhooks or an API read to reconcile the state you need.
Removing or renaming an action, changing its meaning or changing its required resource IDs is a breaking change. Resource ID properties in the TypeScript interface are optional so future actions can supply different IDs; check that an ID is present before using it.
Keep a long-running session active
Sessions expire after 60 minutes. Five minutes before expiry, onSessionExpiring runs so you can replace the session without interrupting the user.
Create the replacement on your server with a new Idempotency-Key, then pass its token to replaceSession():
const replaceEmbeddedSession = async () => {
const replacement = await createEmbeddedSession();
if (!payrollKit.current) {
await revokeEmbeddedSession(replacement.id);
return;
}
try {
await payrollKit.current.replaceSession(replacement.sessionToken);
} catch (error) {
await handleSessionReplacementError(error, replacement);
}
};
Retrying with the same replacement token succeeds if it is already active.
On success, PayrollKit preserves the current view and unsaved input, switches to the replacement and revokes the earlier session. Record the replacement id as the page's current session on your server and mark the earlier session as revoked.
If replacement fails:
- For
network_unavailable, retryreplaceSession()with the same token. Do not create another session. - For
session_replacement_failed, revoke the unused replacement. The earlier session remains active until it expires. - If no replacement succeeds before expiry, PayrollKit clears its content and calls
onSessionEndedwithexpired.
expiresAt is the definitive expiry time. A suspended tab may delay onSessionExpiring, so you may replace the session earlier using that value.
Errors and recovery
PayrollKit separates method errors, asynchronous component errors and session endings:
- Catch errors from
navigate()andreplaceSession()where you call them. - Use
onErrorfor component errors that happen outside a method call. - Use
onSessionEndedwhen a session expires or is revoked.
A rejected method does not also call onError, and an expired or revoked session does not call onError. If a session ends while a method is pending, the method rejects with session_ended so the call does not remain pending; onSessionEnded still reports whether the session expired or was revoked.
Problems with Employer input or payroll actions are shown and resolved inside PayrollKit rather than reported as PayrollKitError.
Use the error code to decide what to do. Its message is safe to display, but the wording may change and must not control application behaviour. Include the optional requestId when contacting support.
PayrollKit may add error codes in a backwards-compatible release. Handle the codes you recognise and provide a generic fallback for any others. For an unrecognised rejected method error, keep the current Partner route and offer a retry. For an unrecognised onError value, leave the component visible and record its requestId when present. Removing a code or changing its meaning is a breaking change.
onError is optional. PayrollKit applies the documented recovery whether or not you provide it. If the component cannot continue, it removes payroll data and displays a safe error state before calling onError. That state remains if the callback is omitted or throws. Use onError only when your surrounding page also needs to respond or record the problem.
Errors reported through onError
| Code | Behaviour and recovery |
|---|---|
component_load_failed | PayrollKit does not become ready or display payroll data. Remount once with the same valid token. If it fails again, check the registered origin and frame-src policy before contacting support. |
session_invalid | PayrollKit does not become ready or display payroll data. Create a new session. If that also fails, check that the page uses the expected environment and registered origin before contacting support. |
session_already_mounted | This component does not become ready, but the existing component remains active. Unmount the other component before reusing its token, or create a separate session. |
session_token_changed | Payroll information is cleared and this component cannot become ready again. Remount with the correct session. For future renewals for the same user and Employer, use replaceSession(). |
unsupported_component_version | PayrollKit does not become ready or display payroll data. Upgrade @payrollkit/react or contact PayrollKit support. |
View errors
During startup, these errors call onError, open HOME and then call onReady. After startup, navigate() rejects and the current view remains unchanged.
| Code | Behaviour and recovery |
|---|---|
invalid_view | Correct the view or required ID before trying again. |
record_not_available | Correct the resource ID or return the user to HOME. |
Rejected method calls
| Code | Behaviour and recovery |
|---|---|
component_not_ready | The component continues loading and the method is not run. Wait for onReady before trying again. |
component_unmounted | The component is unmounted and sends no more callbacks. Stop handling the call; mount another component only if the page still needs payroll. |
navigation_timed_out | The current PayrollKit view remains unchanged. Keep the current route and offer a retry. |
network_unavailable | The current view and unsaved input remain. Retry navigate(), or retry replaceSession() with the same replacement token because its result may be uncertain. |
session_replacement_failed | The earlier session, current view and unsaved input remain. Revoke the unused replacement and retry only after checking its user, Employer and origin. |
Unexpected errors
| Code | Behaviour and recovery |
|---|---|
unexpected_error | The method rejects when the failure is tied to a method call; otherwise PayrollKit uses onError. Payroll information is cleared and the component cannot continue. Remount once with the same valid session, then contact support with the requestId if it happens again. |
When the session ends
| Code | Behaviour and recovery |
|---|---|
session_ended | A pending method rejects with this code while onSessionEnded reports the reason. Payroll information and unsaved input are cleared, so stop handling the method call and follow onSessionEnded. |
Browser behaviour
PayrollKit handles the browser behaviour needed to fit securely and accessibly into your page.
- You can use
@payrollkit/reactin a server-rendered app. It waits until the page is running in the user's browser before loading PayrollKit. - PayrollKit checks session validity automatically. If a session expires or is revoked, PayrollKit clears the component and calls
onSessionEnded. No Partner polling is required. - PayrollKit adjusts its height automatically, manages keyboard focus during navigation and dialogs, and supports screen readers.
Appearance
Use the theme property to match PayrollKit to your application:
| Setting | Options | Default | What it changes |
|---|---|---|---|
appearance | light, dark, system | light | The component's colour mode. |
accentColor | Six-digit hex colour | #171717 | Interactive elements and highlights. |
Partner logos and product names are configured during setup. Where the out-of-the-box options do not meet your needs, we will work with you during your integration to understand what you need and confirm what we can support.