Payment Request Events
Seven events cover the life of a payment request. They all carry the same payload shape, so one handler can serve all of them — switch on the event field to decide what to do.
| Identifier | Fires when |
|---|---|
paymentRequestIssued |
A worker submits a payment request. |
paymentRequestApproved |
The company approves it, or auto-approval applies. |
paymentRequestRejected |
The company rejects it. |
paymentRequestPaid |
The company pays it. |
paymentRequestCancelled |
It is cancelled. |
paymentRequestWorkerPaidOut |
The worker has been paid out. |
paymentRequestRecruiterPaidOut |
The staffing agency has been paid out. |
Webhook payload
{ "event": "paymentRequestIssued", "data": { "paymentRequest": {}, "worker": {} } }
Event Identifier
The event field carries one of the seven identifiers above. It is the only part of the payload that differs between them.
paymentRequest
{ "id": "QmlsbDoxMjM0", "hireId": "SGlyZToxNTY4NTA=", "contractId": "Q29udHJhY3Q6MTIzNA==", "companyId": "Q29tcGFueTo0MjA=", "workerId": "V29ya2VyOjIzNDU2", "recruiterId": null, "jobId": "Sm9iOjc5MTM=", "jobName": "Project Coordinator", "currency": "USD", "netAmount": 440000, "grossAmount": 550000, "taxAmount": 110000, "rate": 550, "rateType": "daily", "hours": null, "days": 8, "weeks": null, "comments": "August timesheet", "purchaseOrderNumber": "BA-1234-5678", "startDate": "2026-08-01", "endDate": "2026-08-31", "issuedAt": "2026-09-01 09:14:22", "approvedAt": "2026-09-02 11:03:10", "rejectedAt": null, "paidAt": null, "cancelledAt": null, "status": "approved", "workerPaidOutStatus": "unpaid", "recruiterPaidOutStatus": null, "owners": [ { "name": "Alex Kim", "email": "[email protected]" } ] }
| Field | Notes |
|---|---|
id |
Global ID of the payment request. |
hireId, contractId, companyId, workerId, jobId |
Global IDs for looking the related objects up through the GraphQL API. |
recruiterId |
null unless a staffing agency is attributed to the hire. |
netAmount, grossAmount, taxAmount |
Integers in minor units of currency. |
rate, rateType |
The rate the request was calculated from. |
hours, days, weeks |
Just the unit matching rateType is populated; the others are null. |
startDate, endDate |
The period the request covers, YYYY-MM-DD. |
issuedAt, approvedAt, rejectedAt, paidAt, cancelledAt |
Timestamps in 24-hour time (YYYY-MM-DD HH:mm:ss), null until the transition happens. approvedAt is also set by auto-approval. |
status |
One of new, approved, due, reminded, paid, prepaid. |
workerPaidOutStatus, recruiterPaidOutStatus |
paid, unpaid, or null when not applicable. |
owners |
The hire’s owners, as name and email only. |
Warning
status is the payment request’s own status and does not map one-to-one onto the event names. A request can be prepaid because the worker was paid early through factoring or payroll, without a paymentRequestPaid event having fired for the company. Read the field rather than inferring state from which event arrived. Both fields are also webhook-only vocabularies, despite having GraphQL namesakes: status shares only approved with the API’s PaymentRequestStatus, and the payout fields are paid/unpaid only — not the API’s PaymentRequest.workerPayoutStatus, which is a separate field also carrying prepaid, due and overdue.
worker
The worker object is the same shape used by the other webhook events:
{ "id": "V29ya2VyOjIzNDU2", "name": "Peter Bishop", "firstName": "Peter", "lastName": "Bishop", "middleName": "", "email": "[email protected]", "phone": "+16955024", "location": { "name": "Home address", "address": "150 Maplewood Drive, Suite 200", "city": "Springfield", "zipcode": "62704", "country": "US", "county": "", "state": "IL" }, "businessEntity": { "entity_type": "common_business", "label": "Bishop Ltd.", "company_no": "987654321", "company_name": "Bishop Ltd.", "tax_no": "ST-1234567890" } }
Handling these events
- Amounts are in minor units. Treat
grossAmount: 550000inUSDas $5,500.00. - Use
idas your idempotency key together withevent, since the same payment request produces several events over its life. - The payload deliberately carries a minimum of information. For anything beyond these fields, call the GraphQL API with the IDs supplied. See Handle Webhooks.