Payloads & Event Types
The structure of a Factorial webhook delivery and the events you can subscribe to.
A webhook delivery is an HTTP POST whose body is the JSON representation of the resource associated with the event. The fields are at the top level of the body — there is no wrapping envelope, and the shape depends on the subscription_type.
POST https://yourapp.com/webhooks/factorial
content-type: application/json
x-factorial-wh-challenge: 2zal4e6d
{ ...the resource that the event happened to... }
The payload for each event is documented individuallyEvery subscription type in the Webhooks section of the API Reference lists its exact Callback payload, field by field. Treat that as the source of truth for the event you are integrating.
The payload is built using the api_version set on the subscription, so its shape stays stable even as newer API versions ship. Respond with a 200 to indicate the data was received successfully.
Example: employee event
A delivery for employees/employee/update carries the employee record:
{
"id": 2,
"access_id": 1,
"first_name": "Ana",
"last_name": "Blanco Perez",
"full_name": "Ana Blanco Perez",
"preferred_name": "Anita",
"birth_name": "Anna",
"gender": "female",
"identifier": "QKG587532Y",
"identifier_type": "passport",
"identifier_expiration_date": "2033-12-31",
"email": "[email protected]",
"login_email": "[email protected]",
"personal_email": "[email protected]",
"communications_email": "[email protected]",
"birthday_on": "1990-06-06",
"nationality": "es",
"country_of_birth": "Spain",
"birthplace": "Barcelona",
"address_line_1": "Calle Adaro 10 1 A",
"address_line_2": null,
"postal_code": "27004",
"city": "Santander",
"state": "Cantabria",
"country": "es",
"phone_number": "657483987",
"bank_number": "ES6220809324751871912999",
"bank_number_format": "iban",
"swift_bic": "CAGLES2M510",
"social_security_number": "150126298420",
"company_id": 1,
"company_identifier": "bb9d281e",
"legal_entity_id": 1,
"location_id": 1,
"default_work_area_id": 1,
"manager_id": 1,
"timeoff_manager_id": 1,
"age_number": 35,
"pronouns": "She/Her",
"disability_percentage_cents": 1200,
"attendable": true,
"active": true,
"seniority_calculation_date": "2024-10-07",
"contact_name": "Laura Delgado",
"contact_number": "647384950",
"is_terminating": false,
"terminated_on": null,
"termination_reason_type": null,
"termination_reason": null,
"termination_type_description": null,
"termination_observations": null,
"created_at": "2010-10-06T00:00:00.000Z",
"updated_at": "2026-10-01T10:32:11.000Z"
}| Field | Notes |
|---|---|
id | Identifier of the resource — here, the employee. It is not a per-delivery identifier, so the same id arrives on every future event for that employee. |
created_at | When the resource was created, not when the event fired. |
updated_at | When the resource was last modified. For update events this is effectively the event time. |
There is no unique delivery identifierBecause
ididentifies the resource rather than the delivery, you cannot deduplicate retries on it alone. Design your handler to be idempotent by converging on the resource's current state instead — see Make your handler idempotent.
Payloads can contain personal dataDeliveries may include personal and financial fields such as national identifiers, bank details and social security numbers. Terminate TLS properly on your endpoint, restrict who can read your logs, and store only the fields your integration actually needs.
Event naming convention
Subscription types follow a domain/resource/action pattern:
timeoff / leave / approve
│ │ │
│ │ └── the action that happened
│ └── the resource it happened to
└── the Factorial domain it belongs to
You pass this exact string as subscription_type when creating a subscription.
Commonly used events
| Event | Description |
|---|---|
employees/employee/create_with_contract | A new employee was created together with a contract |
employees/employee/update | An employee record was modified |
employees/employee/terminate | An employee was marked as terminated |
employees/employee/unterminate | A termination was reverted |
contracts/contract_version/create | A new contract version was created for an employee |
contracts/contract_version/update | An existing contract version was modified |
timeoff/leave/create | A leave request was submitted |
timeoff/leave/approve | A leave request was approved |
timeoff/leave/reject | A leave request was rejected |
attendance/shift/clock_in | An employee clocked in a shift |
ats/candidate/create | A new candidate was created |
bookkeepers_management/incidence/update | A compensation incidence was updated |
api_public/webhook_subscription/create | A webhook subscription was created |
This is not the full listThe table above covers the events used by most integrations. The complete, always-current list of subscription types — with the callback payload for each — is in the Webhooks section of the API Reference.
Request headers on a delivery
| Header | Always sent | Description |
|---|---|---|
x-factorial-wh-challenge | Only if a challenge was set | Echoes the subscription's challenge value so you can verify the request. |
x-factorial-author-id | No | ID of the employee or company that triggered the event. |
x-factorial-author-type | No | Either employee or company. |
Author headers are sent only when the author can be safely exposed to the subscriber; otherwise they are omitted. They are metadata only and never change the body.
Next step
See Webhooks Policies for what happens when a delivery fails, and Best Practices & Troubleshooting for how to build a handler that copes with it.
Updated 9 days ago

