How a client signs in to the company payroll link
Who reads this: payroll staff who answer client questions about signing in Status: built on branch
feat/payroll-link(SIGN-1 to SIGN-4 and LINK-0, 26 Oct 2026). Result: a registered contact of the company signs in with a code sent to their email; holding the link alone opens nothing.
Each company has one lasting link to its payroll page, at …/portal/link/<token>. Nothing in the token names the company or a person; it is made from the link's record and the server's secret, so the same link can be sent again.
How long the link lasts
- It works until it is unused for 30 days. Each sign-in, and each day someone signed in uses it, moves that end 30 days on.
- Making a new link (sending the onboarding email for a company whose link was stopped, for example) stops the old one and signs everyone out of it.
- Revoking stops it at once: everyone signed in is signed out, and any code not yet used stops working. Who revoked it and why is recorded.
- On an expired link, or an old link a newer one has replaced, the page offers Send me the link: the person types their email, and if it is registered for the company they get the current link by email (a new one when it had expired). At most one an hour per contact. Anyone else gets the same answer and no email.
- On a revoked link the page says the payroll team stopped it and to ask them; it offers nothing else.
Documents the client uploads
Signed in, the client can Upload a document: PDF, JPG, PNG, HEIC, WEBP, Word (DOCX), Excel (XLSX, XLS) or CSV, up to 20 MB each. A wrong type, a file renamed to look like another type, or an over-size file is refused before it is stored. Every file is then checked for viruses before anything reads it; the client's list shows Received, being checked for viruses, then Received, or Could not be accepted: it did not pass the virus check. A file that fails goes to quarantine and the Payroll Leads are told. See Virus scanning of uploads.
The client first says what the document is (resignation letter, pay change or bonus letter, offer letter, employment contract, claim form, attendance or leave report, NRIC copy, work pass / FIN copy, bank details, NS letter, IR21, or something else); the upload button then reads Upload for October 2026, the month shown on the page. Once the virus check passes, the file becomes a document of that month and the payroll reader reads it exactly as it reads a document sent by message or added by staff: it makes or completes the month's changes, and files the document under the month it is about (a change letter by its effective date, an offer or contract by its start date, an attendance sheet by its period, a resignation letter by the last working day). If that earlier month is already final, the Payroll Leads and the month's preparers are emailed and the document stays on the current month. If it has already been sent to Infotech (Exported or Output imported), the document is filed there as a late correction, that month's export is marked out of date, and the Leads and preparers are emailed (details). Something else is filed on the month for the payroll team, unread. A file sent to answer a request card is not read: staff check it against the card.
The client's list then says how each file stands: Being read, Read as a resignation letter for September 2026, With the payroll team for October 2026, or Could not be read. The payroll team will look at it. What was read shows up under To do as Please confirm questions: a money figure stated by that one document (see What a document reading may decide), and, for an attendance or leave report uploaded on the link, each person's unpaid leave and overtime ("Grace Tan's unpaid leave in October 2026 is 3 days"), which the client confirms or corrects by typing the number.
Send the link (staff)
On a payroll company's People tab, the Payroll link card (payroll staff only):
- Check who can sign in. A contact needs an email on record and a payroll right; anyone who cannot is shown greyed with the reason (no email on record, no payroll right, switched off).
- Press Send the onboarding email (no link yet) or Send the link again (there is one), tick the people, and press Send. The first send makes the company's link and emails it with the onboarding message; later sends email the same link again, so nobody is signed out and a saved link keeps working.
- The link is never shown on the screen: it goes only by email, so it does not end up pasted in a chat or a ticket.
- The email goes from the payroll mailbox, through Chatwoot, like the month's request (since 27 Oct 2026): the client can reply to it, and the reply lands on the company's thread. The console's message history shows the link as
…/portal/link/[link]; Chatwoot keeps the email as sent. The link alone opens nothing: signing in needs a code sent to the person's own email. - If the payroll mailbox cannot send (none chosen in Settings › Integrations, or it has lost its connection), Send refuses with the reason and nothing is sent. If it fails later in the background, the Payroll Leads and the company's executive get an alert ("The payroll link email to … could not be sent"): fix the cause and send it again. It is never sent twice by a retry.
The card says where the link stands (live since when and sent by whom, last used, when it stops if unused; or revoked by whom and why; or expired), and lists every sign-in: the person, when, which kind of browser, when they were last active, and how it ended (signed out, timed out, link revoked, link replaced, contact removed). Every time on the card is Singapore time, labelled SGT, whatever clock the viewer's computer keeps (since 28 Oct 2026, stage review SX-12); so are the request cards' answered on times, the company Files list's dates, and on the client's link page the lockout time (try again after …), the upload times and the approval date.
Revoke the link (console admins only)
Press Revoke and say why (recorded). The link stops at once, everyone signed in is signed out, and any code not yet used stops working. To give the client a link again, send it: that makes a new one. Payroll Executives and Leads see Only a console admin can revoke the link.
What the client does
- Opens the link. The page shows the company's name and asks for their email.
- Types the email the payroll team has for them and presses Send me a code. The page always answers the same way: "If that email is registered for this company, a six-digit code is on its way to it." It never says whether the email is registered, so a forwarded link tells a stranger nothing.
- Enters the six-digit code from the email ("Your sign-in code for <company>'s payroll page"; the code is in the body, never the subject).
- Is signed in: the page shows "Signed in as <name>", Sign out, a To do list, and the company's employees (read-only until onboarding opens).
The To do list
One card for each thing the payroll team still needs, each asking for exactly one thing. A missing detail takes a typed answer, checked by the same rules as staff entries (a wrong NRIC check letter or a two-digit year is refused before it is sent). A document card takes an upload, scanned for viruses before anyone opens it. A bank-account-name card takes a corrected name or an upload of proof. A card the team sent back shows their reason. Sent answers sit under Sent, waiting for the payroll team to check until a member of staff confirms them. A please confirm card shows a value read from a document ("Grace Tan's new monthly salary from 1 Nov 2026 is SGD 4,500.00"): the client presses Yes, that is right, or No, it should be… and gives the right value; for a month's figure the corrected document must be attached too (What a document reading may decide).
Confirming the company details (ONB-1, since 29 Oct 2026)
Signed in, the client sees one part of the portal at a time (since 29 Oct 2026, evening). While setting up, the parts are the three steps on a stepper band across the top, 1 Company details, 2 Employees, 3 Submit the list, each marked Done, You are here or what it is for, with a progress bar; any step opens at any time (Kabeer, 29 Oct 2026: nothing has to be finished first), and the buttons at the bottom go to the step before and after. The company facts are shown in groups: On the register, Financial year, Payroll timing and Working with the payroll team; the logo card shows a preview of the stored image once it has passed its check. The company and the signed-in person sit top right, the first month's opening date is said on the first step, and each thing on a step is its own card (numbered on the first and third steps; the Employees step is one card that offers two ways in, one under the other and each explained: Type them in (one person at a time, Add an employee) and Upload a file (several at once; an Excel list, one row per employee under a heading row, Upload an Excel list; or the firm's Employee Information Form as a Word file, one per person, Upload the firm's form). Under them the list shows a running number, name, code and status, with the summary line giving only the total.) After the payroll team approves the list the tabs become This month, To do, Company, Employees and Documents. Request cards raised by a submitted list show on the Submit step until then. Until they confirm it, it opens as a form; it says Please check these details before adding your employees.
- What it shows: the company name, UEN, registered office, financial year end, the day salaries are paid (from Settings › Payroll, or Not on record yet) and the day monthly inputs are due. These are read from the company record and the Payroll settings; nothing on the link edits them.
- Needs a correction (until 29 Oct 2026 This is wrong / I can give this): the client ticks the fact and types what it should be. The UEN and the registered office are different: when none is on record they are a plain box the client types into, which is still a correction for staff. That is a correction for staff, not a change: the company record keeps its value, the client sees You said: … (with the payroll team), and the correction waits on the staff side (below). A value equal to what is on record is not a correction.
- Who makes the salary transfers? The one answer the confirmation needs. We pay our employees ourselves makes every employee's bank details optional (the bank details needed cards close by themselves); The payroll team makes the transfers keeps them required. Until the client answers, bank details count as required.
- CorpPass access has been given is a tick the client can leave for later and come back to. Un-ticking it is recorded too.
- Logo for payslips is optional: a PNG, JPG or WEBP image, uploaded like any other file (judged before storing, then virus-checked), kept as the company's logo. Replacing it keeps the old file.
- Confirm these details saves it all and is recorded as the client's act (who, when, from which sign-in). After that the card shows the answers with an Edit button (an outlined button, not a text link, since 29 Oct 2026), and the client can change them any time: a later change is a new confirmation.
Staff side. On the company's People tab, under the Payroll link card, Company details from the client shows: confirmed by whom and when; who pays salaries (and what that means for bank details); whether CorpPass access was given; the logo (Open once its virus check passed); and each open correction with On record beside The client says. Change the company under Settings, then press Handled: the client's card then shows the payroll team has dealt with this. The list of what is outstanding reads: not confirmed yet; N corrections to handle; CorpPass not given; no logo. Payroll Executives, Leads and admins see the card; other roles are refused.
Adding employees and submitting the list (ONB-2, since 29 Oct 2026)
A new client's page opens with a Setting up banner that names the step: Step 1 of 3: check and confirm the company details; Step 2 of 3: add each of your employees; Step 3 of 3: N employees added … Submit the list for the payroll team to review; then Your employee list is with the payroll team for review. The banner goes once staff approve the list.
- Add an employee (on the Employees card, while the list is not yet approved) opens a form in six groups, each its own card with the boxes in two columns: Who they are, Identity and status, How to reach them, Their job, Pay and Bank account (since 29 Oct 2026, evening; before that one long column in Infotech's order). The Bank account group is not shown at all when the client makes the salary transfers themselves. A box marked
*is needed before the person is complete; a pill at the top counts them (3 of 17 needed details given), and a box that fails its check turns red with the reason listed above the save bar. Which boxes are needed follows the person (work pass only for a foreigner, and so on). Each value is checked as it is typed by the same rule the server runs (a wrong NRIC check letter, a two-digit year, an amount that is not money), and the server checks again: a refused save says which field and why, never the value. Allowances are entered as rows (type, monthly amount, CPF applies) with + Add an allowance and Remove; an empty row stays until typed into, and only rows with something in them are saved (fixed 29 Oct 2026: the empty row used to vanish at once). The employee code is not asked: the payroll team gives it when they key the person into Infotech. Only the full name is needed to save; the rest can come later. The save bar stays at the bottom of the window while scrolling: Save and close returns to the list, Save and add another saves and clears the form for the next person (a line confirms who was saved), Cancel drops the draft. In dark mode the date boxes' calendar icon is drawn in the dark scheme, so it stays visible. - Changing a value: open the employee and press Add or Change beside the field. A hidden number (NRIC/FIN, passport, work pass, bank account, birth date) is typed afresh, never shown to start from. Every value the client gives is recorded as Typed by the client, with who and when; a value already on record that the client changes is kept in the field's history.
- Submit the list for review (on the banner) needs the company details confirmed and at least one employee. It makes a request card for every gap (an employee with a gap is held), records the submission, and alerts the payroll team (bell + email to the Leads and the company's executive: "… submitted their employee list on the payroll link", with the counts, linking to the company's People tab). The client can still add and correct employees after submitting; the team sees the latest list, and Send the updated list tells them again.
- Once the payroll team approves the list (the review step), the Employees card is read-only on the link: gaps go through request cards, a new person is reported as a joiner on the month, and the add/change routes answer The payroll team has approved your employee list.
Staff side. The Company details from the client card on the People tab gains an Employee list line: not added yet / N on the link, M complete, not submitted / submitted for review by whom and when (N then) / approved by whom and when. The review and approval step itself is the next package (ONB-5).
Uploading an employee list spreadsheet (ONB-4, since 29 Oct 2026)
While the list is not yet approved, the Employees card also offers Upload an employee list: an Excel file (.xlsx; a CSV or .xls is refused with a plain message) with a header row and one row per person.
- The file is stored and virus-checked like every upload. Once checked, the page shows how each column was placed on an employee field, by its heading and a fixed list of synonyms (Name, NRIC/FIN, Date of birth, Basic salary, Join date, Gender, Nationality, Race, Religion, Mobile, Email, Designation, Residency, Work pass, Bank details, and so on). No model reads it. A column we could not place says not placed; the client picks the field for it or leaves it out. The Employee code column is recognised but never imported from the client.
- Import makes one employee record per row, read from the document (From a document as the source, the file as its origin). Every value goes through the same rules as a typed one: a value that fails (a wrong NRIC check letter, a salary that is not money, a residency that is not one of ours) is left empty and listed, never guessed; the person is still created. A date typed as text (02/03/1994) is read day/month/year, as the client's own typing is. Identity, bank and money values read from the sheet get a please confirm card (READ-1). A row with no name is skipped; the same NRIC twice in one list is the same person listed twice. A row that looks like someone already on record (same NRIC/FIN as an active employee, else the same full name) is shown before anything is imported, with why, and the client says for each: the same person: fill in what is missing or a different person: add them. Import is refused while any such row is undecided. The system never merges or skips on its own (Kabeer, 29 Oct; the write-up's "duplicates are blocked", "the client chooses").
- The outcome says how many were added, which rows were skipped and why, and which values were left out. Hidden values are never shown in the preview.
Uploading the filled onboarding form (ONB-3, since 29 Oct 2026)
An upload opens inside the Upload a file section, not on a page of its own, with a Close button and a Cancel beside the file button, so the list stays in view; the employee's own page carries ← All employees as a button (29 Oct 2026). Upload a filled form (Employees card, while the list is not yet approved) takes the firm's Employee Information Form as the Word file (.docx) the client was sent, one person per form. A PDF, scan or photo of it is refused here with a plain message: those go under Documents, and are read only once a payroll month exists (and, in production, only once the Singapore-hosted reader is in place: SEC-4, not built).
- The file is stored and virus-checked. Once checked, the page shows We read these details. Please check.: every label the form prints (name, designation, NRIC/FIN, religion, work pass, race, country of birth, gender, date of birth, mobile, marital status, emails, date of commencement, basic salary, contribution fund, bank account name / number / bank / branch) with the value read; hidden values masked. No model reads it: the labels are fixed. Allowances, if any is read but not imported (the form gives an amount and a type with no CPF answer); the client adds allowances on the employee.
- Import writes the values to the employee's record as From the onboarding form, each through the same rules as a typed value; a value that fails is left empty and listed. Identity, bank and salary values get a please confirm card: As read from the onboarding form. Residency is not derived from nationality or the pass type: the form does not ask it, so the client answers its card.
- A form for a person who may already be on record (same NRIC/FIN as an active employee, else the same full name) is shown as such, with why, and the client decides: the same person fills only the fields still empty on their record (never overwrites); a different person adds them. Import is refused until they choose. A form whose UEN is another company's is refused.
Approving the employee list, and the first month (ONB-5, since 29 Oct 2026)
Nothing a client adds on the link sets anyone's pay until the payroll team approves the list.
- Where: the Company details from the client card on the People tab ends with Employee list to approve: every active employee with their code (or none yet), monthly salary, residency, birth month (the CPF age band; the day is never shown), PR since (for a PR), bank account (On record / Missing / Not needed when the client pays salaries), and their standing: Complete · goes on the first month, or N missing: … · held until complete. Since 30 Oct 2026 the same box sits on the first month's page too, above the request cards, whenever the list is not yet approved or someone approved is still held, so staff approve where they work the month; the month's readiness line and its attention list say N employees on the company's payroll link, submitted by the client … Approve the list under Employee list to approve instead of asking the client to send a list. After an approval it also says what changed since (New since the last approval, Changed since the last approval: salary) and lists anyone approved before who is no longer an active record.
- Approve the list (payroll staff): records who approved and when, with a snapshot of the list. The confirm dialog says when the client has not pressed Submit yet (approving then ends their editing on the link); staff may still approve, for a client who never presses it. Every complete employee is put on the company's first payroll month at once if that month is open and not yet sent to Infotech; otherwise they go on when the first month opens. The month's rows carry From the employees you added at onboarding on the client's link, and the month stops asking the client for an employee list. The client can no longer add or change employees on the link (gaps go through cards; new people through the month).
- An incomplete employee is held off the month (write-up §6: a missing mandatory field holds that employee). When their card is confirmed closed, they are placed on the month by themselves, if it is still editable.
- The employee code is the payroll team's to give. It is never a client card and never counts against the client's "complete". An employee approved without one goes on the month under a placeholder (
NEW-…); when staff record the code on the employee record, the month's row takes it, while the month is still editable. The Employees card shows every field in Infotech's screen order, to key the person into Infotech. - A first month whose list came from elsewhere (one the client sent, or carried in) is left alone: approval records the list but does not change that month, and the card says so.
- Approve the changes (after the first approval) re-records the list; it is enabled only when something changed or someone complete is still waiting to be placed. A change to residency or birth month since the last approval sets CPF, so only a Payroll Lead (or an admin) may approve it, the same rule as a roster document; the box says so and the server refuses anyone else.
- The first month and the list (30 Oct 2026). A list the client has submitted counts as received: the month's opening message thanks them for it instead of asking for one. A first month that opens after approval, by the schedule, by staff or by a client's document, takes the approved list as it opens and raises no "first month: no earlier employee list" review for a Lead; that review is only raised when the month truly opens empty.
The staff side of the cards (STAFF-1, since 29 Oct 2026)
- Where: the Requests to the client card on the company page (every card, oldest first) and, new, on the month page, narrowed to that month's cards plus the company-wide ones (an employee record's gaps show on every month).
- Each card says how old it is and when the client is next reminded: today · next reminder 1 Nov 2026 (day 3). The reminder is the month's locked schedule (days 3, 5 and 7 after the client was asked for the month, each naming what is still missing); a card on no month rides on the company's newest open month. No reminder due means the clock has not started (the client was not asked yet) or day 7 has passed and the silence handling has taken over.
- Who answered is named: The client (Mei Tan) answered, or Entered by staff (Priya R) for the client. A staff entry is confirmed by someone else, as before.
- Answering a document card for the client can now attach one of the company's stored files: the client emailed the document, staff filed it on the month, and pick it from the list when they record the answer. The card then carries the file, opens it, and is confirmed by a second person once its virus check has passed. Staff never answer a month's figure for the client (unchanged).
- Entered by staff, on both sides: on the month page each document says Entered by staff (name) for the client or Sent by the client on the link (name); on the client's link, a document staff added to a month appears in Documents as Entered by the payroll team for you (openable), and a card staff answered shows (entered by the payroll team for you) under Sent. The client never sees a staff member's name.
What the client sees of the employees
Each employee shows Complete, N details missing or Needs a check (an account name that does not match). Opening one shows every field in Infotech's order with how each value arrived (Entered by staff, From the month's employee list and so on), never who at the firm entered it or the reasons staff gave.
NRIC/FIN, passport, work pass and bank account numbers show masked to their last four characters (•••••567D), and the date of birth shows nothing of itself (••/••/••••). Show reveals one value; the audit trail records payroll.employee.value_revealed with the contact, the employee, the field and the session (never the value). A contact can show at most 30 hidden values an hour; past that the page asks them to wait.
Reporting this month's changes
On the newest month, until it is finalised, Changes this month → Report a change offers one form per item of the firm's "Payroll Input" email:
| Form | Becomes on the month page | Infotech |
|---|---|---|
| New employee or intern | a new hire (intern = employment type Intern) | basic pay, designation |
| Someone is leaving | a leaver, with the reason; unused leave days and additions or deductions are kept as words | final pay is Infotech's; the words are keyed by hand |
| Pay change or increment | a salary change from a date | #BAS |
| Allowance | a salary change (allowance) from a date | keyed by hand, as today |
| Bonus, AWS, commission or sales incentive | a one-off payment | BONUS, AWS; commission and sales incentive keyed by hand |
| Other deduction | a deduction, with what it is for | DEDUCT |
| Leave taken, overtime or NS days | the employee's attendance card | #UPL, #OT1/#OT15/#OT2; paid leave and NS days keyed by hand |
| Claim | a claim, one line per bill: what it was, supplier, bill date, category, amount | CLAIMS (the lines' total) |
Every value is checked as it is typed and again by the server, with the same rules as the employee record: amounts in full (4,500 or S$4,500.50, never 4.5k), dates as day/month/year with four digits, days and hours in halves, the employee chosen from the month's list. The same money rule applies to a figure read from a document (stage review S2-L04): a negative, zero or three-decimal amount read from a letter or claim form is not put on the card; the card (or, when nothing else was read, the file) carries the warning Not used from the <document>: the <figure> read as "…", for staff to check against the document.
A typed pay change, new employee or leaver dated in an earlier month that is already final or with Infotech stays on the month it was typed on (the client reports on the newest month), but its card carries The client typed this change … dated …, in <month>, which is already final (or with Infotech), and that month's Payroll Leads and preparers are emailed, as for a late letter. A date in an earlier month that is still open says nothing extra: the date is on the card.
Each change becomes a card on the staff month page marked Typed by the client, and goes through the same checks as a card read from a document: a new employee waits for the NRIC/FIN copy and bank details, a leaver for the resignation letter and a pay change for the change letter (the client's list says what each waits for). A value Infotech's file has no column for carries the warning Not in the Infotech file: key this into Infotech by hand. Duplicates are refused: a second leaver for one person, the same change sent twice. A second claim with the same total is not refused (two claim forms are two reimbursements): the line check (same supplier, bill date and amount) decides at validation. Two identical sends arriving together (a browser or proxy retry) are taken one at a time, so the second is refused as the same change. If a document already gave the same figure, the client's typed figure counts as their confirmation of it; a different figure is refused and the client is pointed to the Please confirm request, where a figure changes only with the corrected document.
The other order (the client typed first, then a letter for the same change arrives with a different figure or date): the client's figure stays on the card, the letter is attached, and the card carries The client typed this change … and the change letter sent for it disagrees: … is SGD 4,500.00 as typed and SGD 4,200.00 on the change letter, for staff to settle with the client. (The client is not yet shown the two side by side on the link: see Not built yet.)
The same payment in two shapes. A bonus the client types and the same bonus in a letter, or a typed claim and a claim form read as one total, for the same employee and amount, put This may be the same payment as another change on this month on both cards. Neither is refused (two payments of one amount happen); approving either means naming the warning, and rejecting one closes its warning.
Typed leave or overtime for someone who already has an attendance card changes only that figure on the card, which goes back for review.
Claims, line by line (MON-6). A typed claim takes one line per bill, with the firm's seven categories (meals/pantry, transportation, postage/stationery, travelling, entertainment, accommodation/communication, miscellaneous); its total is the sum of the lines. After sending, each line in Changes this month shows Receipt needed with Attach receipt: the receipt is checked for viruses, filed on the month as evidence (not read) and attached to the line. Until every line has one, the month card carries the warning A claim line has no receipt yet, naming the lines. The firm's claim spreadsheet uploaded as Claim form is read by its column headings with no model: a line per row with an amount, the category from its column, date cells as dates, the employee from the Employee Name row; a difference from the sheet's own total is a warning. A bill date written as text that reads both ways (03/04/2026) is never guessed: the line keeps no date and the client is asked which it is. A PDF or photo of a claim form is still read as one total. Two claim lines with the same supplier, bill date and amount, this month or in an earlier month, raise the Lead-only duplicate receipts check.
Below the form, the client sees their changes this month and how each stands (With the payroll team, Waiting for: …, Confirmed, Not used, Withdrawn): what they typed; typed figures merged into a document's card (only the figures they typed are shown, marked You typed this: it was added to the change made from your document); and changes made from documents they uploaded on the link this month, marked Read from your <resignation letter> with what was read (dates included) and a link to open the document (once it has passed its virus check). Withdraw is there until staff confirm the change, for what they typed on its own only: a change read from a document is changed with a corrected document. The audit trail records payroll.item.client_typed, payroll.item.client_withdrew and payroll.item.client_agreed_figure.
Withdrawn after the cutoff. Withdrawing still marks the change Not used for the client, but after the cutoff its warning stays open for staff, now reading Withdrawn by the client after the cutoff, to acknowledge. Before the cutoff its warnings close with it. On the staff month page a withdrawn change shows Withdrawn by the client, apart from staff's own Rejected, and the group's count reads N withdrawn by the client. A typed figure merged into the same card after the cutoff updates its one warning instead of adding another.
Answering the month, and the cutoff (MON-7)
Under Changes this month the client answers the month:
- No changes this month, when they have no changes on the month: it tells the payroll team nothing has changed since last month. Not offered on a company's first month (there is nothing to be the same as).
- Review and submit, once they have changes on the month (typed, added to a document's card, or read from a document they uploaded on the link this month): it lists them, and Submit: that is everything for this month tells the team nothing else is coming. A client who only uploaded a resignation letter is offered Review and submit, not No changes.
Either answer raises the same staff alert a chat answer raises (Client says no changes this month or Client says nothing else is coming); staff then confirm intake as before. The answer is kept with a fingerprint of the month as the client saw it (the employee list and the month's changes). If the figures change afterwards (a staff correction, a new change, a corrected list), the page says This month has changed since then: please check it and answer again, and the staff alert says the client was asked again. A receipt attached to a claim line after Submit is evidence, not a figure, so it does not make the answer stale.
Confirming intake past a stale answer (stage review S2-L06). When staff confirm intake (the review inbox's Confirm intake, or the button on a month no run drives) and the client's answer no longer matches the month, the confirmation is refused with The client answered “No changes this month” … but the month has changed since, the staff alert is marked stale on the spot, and the console asks Why can intake be confirmed anyway?. Confirming again with a reason goes through; the reason is kept as the review decision's note, or on the payroll.intake.confirmed_by_staff audit row. The client is not messaged about it: staff confirm intake anyway.
The cutoff is the company's Inputs due by day (Settings › Payroll, the 18th unless changed). Up to and including that day changes are free: to correct one, the client withdraws it and sends it again, and both versions stay on record. From the next day, and once the month is with the payroll team, a change is still taken, but the page says it goes to the team for approval and the change carries the warning The client sent this after the cutoff, which staff name when approving it; the month is checked again as for a late document. Once the month is finalised it is locked: This month is final: nothing more can be sent for it here.
Approving the month on the link (APR-L)
When a month is in client review and staff have sent the approval request, the month on the link shows October 2026 payroll: waiting for your approval, the approval report to open, Pay withheld pending IRAS tax clearance (IR21) with the leavers whose pay is held back, and Moves to next month: the changes moved to next month when the request was prepared (the same list the approval email gives), every change still held for a document, and every open request (in plain words, never a figure).
Only a contact allowed to approve payroll sees the buttons; others read Only Kavya Iyer can approve the payroll.
- Email me a code to approve sends a fresh six-digit code to the approver's email, even though they are signed in. It is kept apart from sign-in codes, lasts 10 minutes and works once.
- The approver types the code and ticks Approve the whole October 2026 payroll for Acme Pte Ltd, as in request 2 (spade-report-v2.xlsx)?, then Approve.
The decision is for the request on the page. The page remembers which request the approver started on (when they asked for the code, or chose Ask for changes) and sends it with the decision. If that request was replaced since (the payroll changed and a new request went out), the page says This request was replaced by request 2. Open its report and look again before you decide and the buttons stay off until they go Back and start again; the server refuses such a decision too (This request was replaced by request 2, with updated figures. Look at the new one (open its report again) before you decide.), before the code is used. The code itself is not yet tied to one month or one request (that needs two new columns on the codes table): a code asked for on one month would still approve another month waiting on the same link.
Wrong codes count toward the same lockout as sign-in (six in a row: 15 minutes, and the Payroll Leads are told). The approval binds the figures in the request: if the payroll changed since it was sent, the request is withdrawn and an updated one drafted, as before. Ask for changes takes a reason and no code.
The record says it was approved on the payroll link with a fresh code (identity LINK_SESSION_FRESH_CODE, it was LINK_POSSESSION on the one-off link), with the contact, the session, the browser and the code. The approval note saved with it lists what the page showed: Approved on the payroll link. Moves to next month: … Pay withheld pending IRAS tax clearance (IR21): …. Afterwards the month shows Approved by Kavya Iyer on 27 Oct 2026 (request 1); when a Payroll Lead approved without the client it shows Approved by our Payroll Lead without your approval, on 27 Oct 2026 (request 1).
When the figures change after the client approved, that approval no longer stands: the month shows Your approval of request 1 was withdrawn because the figures changed after it. An updated request is coming. until staff send the new request, which then shows as waiting.
The approval request and its reminders now carry the company's payroll link when it has one; the one-off approval link still works for a request already out. An "approved" reply by email or chat is never an approval: only the link, the one-off approval link or a Payroll Lead's own decision can record one (a test checks no other code can).
What the payroll messages say (MSG-1)
When the company has a live payroll link, every payroll message that asks the client for something carries it: the month's request, the day 3, 5 and 7 reminders, the reminders while the month waits on the client (action required, revision requested), the closing question, the "more coming" reminder, and "we have everything" when something is still needed. Each also says what waits on the link:
- by email, a list in plain words: "Ravi Kumar: NRIC / FIN number needed", "Grace Tan: please confirm a figure we read", "Arjun Menon: a document we asked for". A card's own title is not used when it could carry a figure.
- by Telegram or WhatsApp, only how many: "3 things are waiting for you on your payroll page." On chat the month's lists become counts too ("2 changes are already with us for November: see your payroll page", "1 document is still needed"), so a chat message names no employee. For a company without a link, chat keeps naming employees as before, since there is nowhere else to read it.
A last check before anything is sent through Chatwoot, whoever wrote the text (a template, a member of staff, a model draft, or the client's own words quoted back): an NRIC/FIN with a valid check letter is masked to its last four characters on every medium, and on chat an amount (SGD 4,500.00, $1,500, 4,200) becomes an amount (see your payroll page) and a run of eight or more digits (an account number) is masked to its last four. Links are left alone. Each masking is logged with counts only.
Who can sign in
An active contact of that company, with a payroll right (can submit or can approve), and an email on record (People tab). A new email still waiting for a second admin's confirmation is not used: the code goes to the email in use. A chat-only contact (no email) cannot sign in.
The limits
| What | Value |
|---|---|
| A code works for | 10 minutes, once. A new code replaces the last. |
| Codes per contact | at most 5 an hour (more requests get the same answer, no email) |
| Code requests per network address | 20 an hour |
| Wrong codes | the 6th wrong code in a row locks that contact for 15 minutes |
| A session ends | after 30 minutes without activity, and after 12 hours in any case |
These are server settings (LINK_CODE_TTL_MINUTES, LINK_SESSION_IDLE_MINUTES, LINK_SESSION_MAX_HOURS, LINK_LOCK_AFTER_WRONG_CODES, LINK_LOCK_MINUTES, LINK_CODES_PER_CONTACT_PER_HOUR), the same for every company.
What staff see
- A lockout puts a bell alert on the Payroll Leads and the company's assigned Payroll Executive, with an email: "<contact> was locked out of <company>'s payroll link after 6 wrong codes in a row". If the contact did not try to sign in, someone else may have their link: check with them, and ask a console admin to revoke it if needed.
- A sign-in from a new browser (one this contact has not signed in from before, including a private window or a cleared browser) emails every registered contact of the company: "New sign-in to <company>'s payroll page", naming who and what kind of browser. Telling the others is on purpose: the main risk is a taken-over mailbox, and an alert sent only to that mailbox protects nothing.
- The audit trail of the company records each code sent (
payroll.link.code_sent), wrong code (payroll.link.code_wrong), lockout (payroll.link.locked), sign-in (payroll.link.signed_in, with the browser), sign-out and page view (payroll.link.viewed, at most once a minute per page). None of them holds the code or the email typed.
When a client says they did not get a code
- Check the People tab: is that the email on record, is the contact active, do they have a payroll right?
- Ask them to check spam, then press Send a new code (a new code replaces the old).
- More than 5 requests in an hour send nothing more until the hour has passed.
- Codes come from the system address: a code is a security email nobody replies to (the rule in Notifications). Which system address is still for the infrastructure owner to confirm (blocker B9).
Not built yet
- Reading a scan or photo of the onboarding form, a contract or an ID copy into the employee record before a payroll month exists. Today such a file is kept and read only once a month exists (through the month's reader, into the month's cards), and in production scans wait for the Singapore-hosted reader (SEC-4). Everything else in Phase 3 (company details, typing, spreadsheet, the Word form, review and approval, the first month) is built. A PDF or photo of a claim form is still read as one total.
- When a letter disagrees with a change the client typed, the client is not yet shown both figures to choose from on the link (stage review S2-L07): staff are told both and settle it with the client.