SwimdraftPricingSign inStart free

Payroll processing BPMN example

Payroll processing is the one back office process where a mistake is visible to every employee at once. Teams map it because it is deadline work with hard external dependencies: input from HR, statutory reporting to HMRC, and a bank submission window that does not move. The recurring failure is exception handling, where late corrections crowd into the final hours before cut off.

The model is a single pool with HR, Payroll team, and Finance lanes. An exclusive gateway with a correction loop guards the gross to net calculation, and a parallel gateway then fans out the three closing obligations, bank file, payslips, and RTI return, which do not depend on each other. Splitting the endgame in parallel is the choice worth noticing.

Payroll processing: BPMN 2.0 diagram
Payroll processing process modelled as a BPMN 2.0 diagram

Standard BPMN 2.0 interchange XML: opens in Camunda Modeler, Signavio, Bizagi, and any other compliant tool.

The process at a glance

Starts when
Pay period cut off reached
Ends with
Payroll completed
Lanes
HR · Payroll team · Finance
Decisions
Exceptions found?

How to read this diagram

Reaching the pay period cut off starts the clock. HR gathers the payroll inputs, the Payroll team processes starters and leavers, and the gross to net calculation runs as a service task. Its output is checked against an exception report, and at "Exceptions found?" the Yes branch corrects the payroll data and loops back to rerun the calculation, repeating until the report is clean. The default No branch releases the results.

A parallel gateway then splits the token three ways with no conditions: Finance submits the BACS payment file, while the Payroll team distributes payslips and submits the RTI return to HMRC. The joining gateway waits for all three before the process ends at "Payroll completed", which guarantees no pay run is declared done with a statutory filing still outstanding.

BPMN elements used

ElementCountIn this diagram
Start event1Pay period cut off reached
End event1Payroll completed
Task1Gather payroll inputs
User task3Process starters and leavers, Check exception report, Correct payroll data
Service task3Run gross to net calculation, Submit BACS payment file, Submit RTI return to HMRC
Send task1Distribute payslips
Exclusive gateway1Exceptions found?
Parallel gateway2
View the BPMN 2.0 XML for this diagram
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="swimdraft" exporterVersion="1.0">
  <bpmn2:collaboration id="Collaboration_1">
    <bpmn2:participant id="pool_pay" name="Payroll processing" processRef="Process_pool_pay" />
  </bpmn2:collaboration>
  <bpmn2:process id="Process_pool_pay" isExecutable="false">
    <bpmn2:laneSet id="LaneSet_pool_pay">
      <bpmn2:lane id="lane_pay_hr" name="HR">
        <bpmn2:flowNodeRef>start_pay</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_collect</bpmn2:flowNodeRef>
      </bpmn2:lane>
      <bpmn2:lane id="lane_pay_team" name="Payroll team">
        <bpmn2:flowNodeRef>t_pay_changes</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_run</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_check</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>g_pay_exc</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_fix</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>g_pay_split</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_slips</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>t_pay_rti</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>g_pay_join</bpmn2:flowNodeRef>
        <bpmn2:flowNodeRef>end_pay_done</bpmn2:flowNodeRef>
      </bpmn2:lane>
      <bpmn2:lane id="lane_pay_fin" name="Finance">
        <bpmn2:flowNodeRef>t_pay_bacs</bpmn2:flowNodeRef>
      </bpmn2:lane>
    </bpmn2:laneSet>
    <bpmn2:startEvent id="start_pay" name="Pay period cut off reached">
      <bpmn2:outgoing>fpay1</bpmn2:outgoing>
    </bpmn2:startEvent>
    <bpmn2:task id="t_pay_collect" name="Gather payroll inputs">
      <bpmn2:incoming>fpay1</bpmn2:incoming>
      <bpmn2:outgoing>fpay2</bpmn2:outgoing>
    </bpmn2:task>
    <bpmn2:userTask id="t_pay_changes" name="Process starters and leavers">
      <bpmn2:incoming>fpay2</bpmn2:incoming>
      <bpmn2:outgoing>fpay3</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:serviceTask id="t_pay_run" name="Run gross to net calculation">
      <bpmn2:incoming>fpay3</bpmn2:incoming>
      <bpmn2:incoming>fpay7</bpmn2:incoming>
      <bpmn2:outgoing>fpay4</bpmn2:outgoing>
    </bpmn2:serviceTask>
    <bpmn2:userTask id="t_pay_check" name="Check exception report">
      <bpmn2:incoming>fpay4</bpmn2:incoming>
      <bpmn2:outgoing>fpay5</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:exclusiveGateway id="g_pay_exc" name="Exceptions found?" default="fpay8">
      <bpmn2:incoming>fpay5</bpmn2:incoming>
      <bpmn2:outgoing>fpay6</bpmn2:outgoing>
      <bpmn2:outgoing>fpay8</bpmn2:outgoing>
    </bpmn2:exclusiveGateway>
    <bpmn2:userTask id="t_pay_fix" name="Correct payroll data">
      <bpmn2:incoming>fpay6</bpmn2:incoming>
      <bpmn2:outgoing>fpay7</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:parallelGateway id="g_pay_split">
      <bpmn2:incoming>fpay8</bpmn2:incoming>
      <bpmn2:outgoing>fpay9</bpmn2:outgoing>
      <bpmn2:outgoing>fpay10</bpmn2:outgoing>
      <bpmn2:outgoing>fpay11</bpmn2:outgoing>
    </bpmn2:parallelGateway>
    <bpmn2:serviceTask id="t_pay_bacs" name="Submit BACS payment file">
      <bpmn2:incoming>fpay9</bpmn2:incoming>
      <bpmn2:outgoing>fpay12</bpmn2:outgoing>
    </bpmn2:serviceTask>
    <bpmn2:sendTask id="t_pay_slips" name="Distribute payslips">
      <bpmn2:incoming>fpay10</bpmn2:incoming>
      <bpmn2:outgoing>fpay13</bpmn2:outgoing>
    </bpmn2:sendTask>
    <bpmn2:serviceTask id="t_pay_rti" name="Submit RTI return to HMRC">
      <bpmn2:incoming>fpay11</bpmn2:incoming>
      <bpmn2:outgoing>fpay14</bpmn2:outgoing>
    </bpmn2:serviceTask>
    <bpmn2:parallelGateway id="g_pay_join">
      <bpmn2:incoming>fpay12</bpmn2:incoming>
      <bpmn2:incoming>fpay13</bpmn2:incoming>
      <bpmn2:incoming>fpay14</bpmn2:incoming>
      <bpmn2:outgoing>fpay15</bpmn2:outgoing>
    </bpmn2:parallelGateway>
    <bpmn2:endEvent id="end_pay_done" name="Payroll completed">
      <bpmn2:incoming>fpay15</bpmn2:incoming>
    </bpmn2:endEvent>
    <bpmn2:sequenceFlow id="fpay1" sourceRef="start_pay" targetRef="t_pay_collect" />
    <bpmn2:sequenceFlow id="fpay2" sourceRef="t_pay_collect" targetRef="t_pay_changes" />
    <bpmn2:sequenceFlow id="fpay3" sourceRef="t_pay_changes" targetRef="t_pay_run" />
    <bpmn2:sequenceFlow id="fpay4" sourceRef="t_pay_run" targetRef="t_pay_check" />
    <bpmn2:sequenceFlow id="fpay5" sourceRef="t_pay_check" targetRef="g_pay_exc" />
    <bpmn2:sequenceFlow id="fpay6" name="Yes" sourceRef="g_pay_exc" targetRef="t_pay_fix">
      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">Yes</bpmn2:conditionExpression>
    </bpmn2:sequenceFlow>
    <bpmn2:sequenceFlow id="fpay7" sourceRef="t_pay_fix" targetRef="t_pay_run" />
    <bpmn2:sequenceFlow id="fpay8" name="No" sourceRef="g_pay_exc" targetRef="g_pay_split" />
    <bpmn2:sequenceFlow id="fpay9" sourceRef="g_pay_split" targetRef="t_pay_bacs" />
    <bpmn2:sequenceFlow id="fpay10" sourceRef="g_pay_split" targetRef="t_pay_slips" />
    <bpmn2:sequenceFlow id="fpay11" sourceRef="g_pay_split" targetRef="t_pay_rti" />
    <bpmn2:sequenceFlow id="fpay12" sourceRef="t_pay_bacs" targetRef="g_pay_join" />
    <bpmn2:sequenceFlow id="fpay13" sourceRef="t_pay_slips" targetRef="g_pay_join" />
    <bpmn2:sequenceFlow id="fpay14" sourceRef="t_pay_rti" targetRef="g_pay_join" />
    <bpmn2:sequenceFlow id="fpay15" sourceRef="g_pay_join" targetRef="end_pay_done" />
  </bpmn2:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_1">
      <bpmndi:BPMNShape id="pool_pay_di" bpmnElement="pool_pay" isHorizontal="true">
        <dc:Bounds x="20" y="20" width="1570" height="500" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="lane_pay_hr_di" bpmnElement="lane_pay_hr" isHorizontal="true">
        <dc:Bounds x="50" y="20" width="1540" height="130" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="lane_pay_team_di" bpmnElement="lane_pay_team" isHorizontal="true">
        <dc:Bounds x="50" y="150" width="1540" height="240" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="lane_pay_fin_di" bpmnElement="lane_pay_fin" isHorizontal="true">
        <dc:Bounds x="50" y="390" width="1540" height="130" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="start_pay_di" bpmnElement="start_pay">
        <dc:Bounds x="112" y="59" width="36" height="36" />
        <bpmndi:BPMNLabel><dc:Bounds x="65" y="101" width="131" height="14" /></bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_collect_di" bpmnElement="t_pay_collect">
        <dc:Bounds x="230" y="37" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_changes_di" bpmnElement="t_pay_changes">
        <dc:Bounds x="380" y="167" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_run_di" bpmnElement="t_pay_run">
        <dc:Bounds x="530" y="167" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_check_di" bpmnElement="t_pay_check">
        <dc:Bounds x="680" y="167" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="g_pay_exc_di" bpmnElement="g_pay_exc">
        <dc:Bounds x="855" y="182" width="50" height="50" />
        <bpmndi:BPMNLabel><dc:Bounds x="834" y="162" width="93" height="14" /></bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_fix_di" bpmnElement="t_pay_fix">
        <dc:Bounds x="980" y="277" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="g_pay_split_di" bpmnElement="g_pay_split">
        <dc:Bounds x="1005" y="182" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_bacs_di" bpmnElement="t_pay_bacs">
        <dc:Bounds x="1130" y="407" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_slips_di" bpmnElement="t_pay_slips">
        <dc:Bounds x="1130" y="167" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="t_pay_rti_di" bpmnElement="t_pay_rti">
        <dc:Bounds x="1130" y="277" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="g_pay_join_di" bpmnElement="g_pay_join">
        <dc:Bounds x="1305" y="182" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="end_pay_done_di" bpmnElement="end_pay_done">
        <dc:Bounds x="1462" y="189" width="36" height="36" />
        <bpmndi:BPMNLabel><dc:Bounds x="1435" y="231" width="90" height="14" /></bpmndi:BPMNLabel>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="fpay1_di" bpmnElement="fpay1">
        <di:waypoint x="148" y="77" />
        <di:waypoint x="230" y="77" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay2_di" bpmnElement="fpay2">
        <di:waypoint x="330" y="77" />
        <di:waypoint x="360" y="77" />
        <di:waypoint x="360" y="207" />
        <di:waypoint x="380" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay3_di" bpmnElement="fpay3">
        <di:waypoint x="480" y="207" />
        <di:waypoint x="530" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay4_di" bpmnElement="fpay4">
        <di:waypoint x="630" y="207" />
        <di:waypoint x="680" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay5_di" bpmnElement="fpay5">
        <di:waypoint x="780" y="207" />
        <di:waypoint x="855" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay6_di" bpmnElement="fpay6">
        <di:waypoint x="905" y="207" />
        <di:waypoint x="960" y="207" />
        <di:waypoint x="960" y="317" />
        <di:waypoint x="980" y="317" />
        <bpmndi:BPMNLabel><dc:Bounds x="911" y="187" width="21" height="14" /></bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay7_di" bpmnElement="fpay7">
        <di:waypoint x="1080" y="317" />
        <di:waypoint x="1110" y="317" />
        <di:waypoint x="1110" y="372" />
        <di:waypoint x="510" y="372" />
        <di:waypoint x="510" y="207" />
        <di:waypoint x="530" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay8_di" bpmnElement="fpay8">
        <di:waypoint x="905" y="207" />
        <di:waypoint x="1005" y="207" />
        <bpmndi:BPMNLabel><dc:Bounds x="911" y="213" width="17" height="14" /></bpmndi:BPMNLabel>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay9_di" bpmnElement="fpay9">
        <di:waypoint x="1055" y="207" />
        <di:waypoint x="1110" y="207" />
        <di:waypoint x="1110" y="447" />
        <di:waypoint x="1130" y="447" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay10_di" bpmnElement="fpay10">
        <di:waypoint x="1055" y="207" />
        <di:waypoint x="1130" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay11_di" bpmnElement="fpay11">
        <di:waypoint x="1055" y="207" />
        <di:waypoint x="1110" y="207" />
        <di:waypoint x="1110" y="317" />
        <di:waypoint x="1130" y="317" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay12_di" bpmnElement="fpay12">
        <di:waypoint x="1230" y="447" />
        <di:waypoint x="1260" y="447" />
        <di:waypoint x="1260" y="207" />
        <di:waypoint x="1305" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay13_di" bpmnElement="fpay13">
        <di:waypoint x="1230" y="207" />
        <di:waypoint x="1305" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay14_di" bpmnElement="fpay14">
        <di:waypoint x="1230" y="317" />
        <di:waypoint x="1260" y="317" />
        <di:waypoint x="1260" y="207" />
        <di:waypoint x="1305" y="207" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="fpay15_di" bpmnElement="fpay15">
        <di:waypoint x="1355" y="207" />
        <di:waypoint x="1462" y="207" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn2:definitions>

Frequently asked questions

Why do the three closing tasks run in parallel?

Submitting the bank file, issuing payslips, and filing the RTI return draw on the same approved results but not on each other, so forcing them into a sequence would only add delay. The parallel join then does the real work: it holds the end event back until all three obligations are met, which is exactly the control you want.

Why does the exception loop return to the calculation rather than to HR?

The correction task fixes data inside the payroll system, so the shortest safe path is to rerun the calculation from there. Looping all the way back to gathering inputs would repeat work that is already done. If your exceptions are usually HR data problems, move the loop target back one step and the model stays valid either way.

Why mix task types across the payroll run?

The types document who or what does the work. The gross to net calculation and the BACS and RTI submissions are system executed, so they are service tasks; checking the exception report and correcting data need human judgement, so they are user tasks; payslip distribution is a send task because its essence is transmitting something to recipients.

Related BPMN examples

Generate your own version of this diagram

Describe how the process works in your organisation: plain English, meeting notes, a transcript, or a spreadsheet. Swimdraft turns it into a spec-validated BPMN 2.0 diagram you can edit in the browser and export as .bpmn, SVG, or PNG.

Start free

No credit card required