Webhooks
Webhooks let you keep employee data synchronized between Huma and any external service or platform you connect.
Table of Contents
- Roles and access
- Multiple integrations
- Supported features
- Can I use webhooks for service "X"?
- How to set up a webhook
- Profile email change event
- Profile events
- Supported fields profile events
- Custom fields
- Position events
- Supported fields position events
- Absence events
- Supported fields absence events
- Company, Team & Location events
- Event actor
- Security & compliance
- FAQ
Roles and access
| Role | Access |
|---|---|
| System role with full access permissions for Organization | Can configure and manage webhook integrations |
💡⛓️💥 Interested in Open API? Read this article.
Multiple integrations
If you need to configure multiple webhook instances, follow the setup guidance provided here.
Supported features
Employee profile events
- Specific event for changing the email used to log in to a profile.
- Automated updates when users are added or deleted — a message is sent automatically whenever a user is created or deleted in Huma.
- Real-time updates when a profile is changed — changes to supported profile fields are sent automatically to the registered URL.
- Export of current profile state, including custom fields — useful for initialising state in the system connecting via webhook.
Position events
- Automated updates when positions are created, updated, or deleted
Absence events
- Automated updates when absences are registered or removed
- Real-time updates when an absence entry is reviewed or otherwise edited
Company, Team & Location events
- Updates when a Company, Team, or Location is created, updated, or deleted
- Membership changes — events are sent whenever users are added to or removed from a Company, Team, or Location
Can I use webhooks for service "X"?
It is common to use a "middle service" between the webhook and a third-party system. This middle service receives the webhook payload and reformats it to match the requirements of the receiving system. Some customers have their IT department build this service internally. Others use external middleware suppliers.
🔗 For a practical example, see this article.
How to set up a webhook
- Go to "Integrations"
- Find Webhooks and click "New connection"
- Click "Set up"
- Enter the URL that should receive the webhook requests and follow the steps
Additional settings:
- Client secret (optional) — if set, each request will include a
huma-hmac-sha256header containing a hash of the JSON body, signed with the secret. Use this to verify that requests come from Huma. - Access token (optional) — if set, each request will include it as a standard bearer token.
- Which events to send — choose one or more of:
- Employee profile events
- Position events
- Absence events
- Company, Team & Location events
- HTTP method — choose between always sending
POST, or using HTTP methods that match the event type:DELETEfor deletions,PUTfor creations,PATCHfor updates, and so on.
💡 All requests include a huma-topic header and a topic property in the JSON body. Both values are always identical.
💡 All request bodies also include an actor property describing who triggered the event. See Event actor.
⚠️ If a request is not handled within 1 second, it will time out. It is recommended to persist the received data and respond immediately, then process the data in a separate step.
Profile email change event
Because the profile email is used to log in to Huma, changing it follows a separate flow and is sent as its own event with topic user-email-update.
💡 Note: a profile can also have a private email address. Changes to the private email are treated as a normal profile field update and are sent as a users-update event. See Profile events and Supported fields below.
The request body contains a single user object with the following fields:
| Field | JSON key | Notes |
|---|---|---|
| User ID | id |
Always present. A unique, stable UUID (RFC 4122). Will not change over the user's lifetime. |
| Previous email | oldEmail |
Always present. |
| New email | newEmail |
Always present. |
| Employment ID | employmentId |
Present if set. Unique at the time of the request, but can be changed or removed later. |
Example:
{ "topic": "user-email-update", "user": { "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "oldEmail": "jesper@godmat.no", "newEmail": "jesper.blom@godmat.no", "employmentId": "1" } }
💡 Please note — uniqueness of employment IDs:
- Huma can guarantee uniqueness if you have always had "Require unique employment IDs" enabled.
- Huma cannot guarantee uniqueness if the requirement has been disabled at any point. Duplicates may exist even if the requirement has since been re-enabled, unless they have been corrected manually.
Profile events
For profile data, the topic will be one of:
users-createusers-updateusers-deleteusers-export
All profile event request bodies include a users array. Each object in the array always contains id, email, employmentId (if set), and status.
Create
Sent as a POST request. Contains full profile objects for all created users.
Example:
{ "topic": "users-create", "users": [ { "id": "09d41e51-0963-4763-933b-8c9df093d653", "status": "ACTIVE", "givenName": "Ida", "preferredName": null, "familyName": "Fossdal", "email": "ida.fossdal@godmat.no", "privateEmail": null, "phone": "+47 955 55 167", "address": { "line1": "Fossvegen 82", "line2": null, "postalCode": "1823", "city": "Bergen", "country": "NO" }, "birthDate": "1993-07-29", "nationality": null, "dietaryRequirements": null, "funfacts": null, "interests": null, "bankAccount": { "type": "national", "number": "12345678903", "country": "NO" }, "gender": "Female", "civilStatus": null, "employmentId": "1", "firstDayOfWork": "2001-09-14", "employmentStartDate": "2001-09-14", "lastDayOfWork": null, "employmentEndDate": null, "terminationNoticeDate": null, "terminationDate": null, "employmentType": "Permanent", "employmentPercentage": 100, "jobTitle": "CEO", "jobDescription": "Make the decisions", "identifications": null, "salary": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "periodUnit": "yearly", "amount": 950000, "currency": "NOK", "fromDate": "2020-01-01", "toDate": null, "current": true, "note": null }, "supervisor": null } ], "actor": { "type": "user", "id": "09d41e51-0963-4763-933b-8c9df093d653", "email": "ida.fossdal@godmat.no", "name": "Ida Fossdal" } }
Delete
Sent as a DELETE request. The users objects contain identifiers only.
Example:
{ "topic": "users-delete", "users": [ { "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "employmentId": "31", "status": "INACTIVE" } ], "actor": { "type": "user", "id": "09d41e51-0963-4763-933b-8c9df093d653", "email": "ida.fossdal@godmat.no", "name": "Ida Fossdal" } }
Update
Sent as a PATCH request. The users objects contain identifiers and only the changed fields.
Example — an employee updating their bank account to use IBAN:
{ "topic": "users-update", "users": [ { "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "employmentId": "31", "status": "ACTIVE", "bankAccount": { "type": "international", "iban": "FR7630006000011234567890189", "bic": "AGRIFRPP" } } ], "actor": { "type": "user", "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "name": "Jesper Blom" } }
Export
Sent as one or more POST requests. Each request contains the full current state of all supported fields for up to 50 users. If your organisation has more than 50 users, the export is split across multiple requests. For example, an export from an organisation with 132 users would result in three requests: two with 50 users each, and one with 32.
⚠️ The field "Children" is not a supported field and will not be included.
Supported fields
| Field | JSON key |
|---|---|
| Email address | email — note: changes to the main email are sent as a separate user-email-update event |
| Status | status |
| Given name | givenName |
| Family name | familyName |
| Preferred name | preferredName |
| Phone number | phone |
| Private email | privateEmail |
| Address | address |
| Date of birth | birthDate |
| Nationality | nationality |
| Dietary restrictions | dietaryRequirements |
| Fun facts | funfacts |
| Interests | interests — either null or an array of strings |
| Bank account number | bankAccount — either null or an object containing type, and two additional fields depending on the value of type. If type is national, the other fields are number and country. If type is international, the other fields are iban and bic. |
| Gender | gender |
| Civil status | civilStatus |
| Identifications | identifications — either null or an array of objects. Each object contains id (Huma-internal ID), type (either national or passport), value (the identification number), and country (ISO 3166-1 alpha-2 country code). |
| Supervisor | supervisor — either null or an object containing id and email |
| Employment ID | employmentId |
Position fields
| Field | JSON key |
|---|---|
| Employment start date | employmentStartDate |
| Probation end date | probationEndDate |
| First day of work | firstDayOfWork |
| Termination notice date | terminationNoticeDate |
| Last day of work | lastDayOfWork |
| Employment end date | employmentEndDate |
| Employment type | employmentType — either null or one of: Permanent, Temporary, Seasonal, Casual, Trainee, Job training program, Consultant, Freelance, Third party, Other |
| Employment percentage | employmentPercentage |
| Job title | jobTitle |
| Job description | jobDescription |
⚠️ Salary (will come as separate events in 2026)
| Field | JSON key | Notes |
|---|---|---|
| Salary | salary |
An object containing salary information. Only the current salary is sent. |
salary.id |
A unique identifier for this salary entry (UUID) | |
salary.periodUnit |
One of hourly, weekly, monthly, or yearly |
|
salary.amount |
A decimal number | |
salary.currency |
An ISO 4217 currency code | |
salary.fromDate |
Always present | |
salary.toDate |
Optional | |
salary.current |
A boolean indicating whether this is the employee's current salary | |
salary.note |
Optional free text |
💡 The salary field is sent through profile event for now.
Custom fields
Any custom fields your organisation has defined will be included in update events when the custom field is the one being updated, and in all export events.
If an event includes custom fields, the user object will contain a custom property — an object with custom field names as keys and their values. If a custom field has no value, it is included as null.
Example for an organisation with two custom fields, "Shirt size" and "License plate":
"custom": { "shirtSize": "XL", "licensePlate": null }
🔗 See custom fields for details on how field names are determined and what value types are supported.
Position events
For position data, the topic will be one of:
positions-createpositions-updatepositions-deletepositions-export
All position event request bodies include a positions array containing an object for each affected position.
Create
Sent as a POST request. Contains the full position object.
Example:
{ "topic": "positions-create", "positions": [ { "id": "59307188-bd2d-431a-aa97-97d2b92222dc", "contractStartDate": "2026-02-18", "contractEndDate": null, "firstDayOfWork": "2026-02-18", "lastDayOfWork": null, "contractCountry": "NO", "contractType": "Permanent", "percentage": 100, "probationEndDate": "2026-08-18", "terminationNoticeDate": null, "title": "CMO", "description": "Oversees the company's marketing strategy.", "endNote": null, "endReason": null, "wantedEnd": false, "note": "Team: Development, Company: Tech Inc. Supervisor: Karin Strand!", "user": { "id": "e902714f-a3f9-4cd1-ab04-980a443d77ae", "email": "user-email@company.com" } } ], "actor": { "type": "user", "id": "a558d25a-99d6-45b1-855c-63b052adc5f3", "email": "admin@company.com", "name": "Karina Strand" } }
Update
Sent as a PATCH request. Contains only the changed fields.
Example:
{ "topic": "positions-update", "positions": [ { "id": "5c1de17b-7e47-4827-a1f3-536bb061c2c4", "contractStartDate": "2021-08-18", "firstDayOfWork": "2021-08-18", "contractCountry": "NO", "contractType": "Trainee", "percentage": 75, "probationEndDate": "2022-02-18", "note": "Team: Development, Company: Tech Inc. Supervisor: Karin Strand!", "user": { "id": "e902714f-a3f9-4cd1-ab04-980a443d77ae", "email": "user-email@company.com" } } ], "actor": { "type": "user", "id": "a558d25a-99d6-45b1-855c-63b052adc5f3", "email": "admin@company.com", "name": "Karina Strand" } }
Delete
Sent as a DELETE request. The positions objects contain identifiers only.
Example:
{ "topic": "positions-delete", "positions": [ { "id": "5c1de17b-7e47-4827-a1f3-536bb061c2c4", "user": { "id": "e902714f-a3f9-4cd1-ab04-980a443d77ae", "email": "user-email@company.com" } } ], "actor": { "type": "user", "id": "a558d25a-99d6-45b1-855c-63b052adc5f3", "email": "admin@company.com", "name": "Karina Strand" } }
Export
When you activate an export from the webhook integration page, you select a time period and which users' positions to include. The result is sent as one or more POST requests, each containing up to 50 position entries.
Example:
{ "topic": "positions-export", "positions": [ { "id": "12425fdc-e743-4e82-84ca-3036d5cbbb0d", "contractStartDate": "2026-01-20", "contractEndDate": "2026-07-20", "firstDayOfWork": "2026-01-20", "lastDayOfWork": "2026-07-20", "contractCountry": "NO", "contractType": "Permanent", "percentage": 100, "probationEndDate": "2026-04-20", "terminationNoticeDate": "2026-06-20", "title": "Software Engineer", "description": "This is a description of the position.", "endNote": "This is an end note.", "endReason": "employee_terminated", "wantedEnd": false, "note": "Team: Development, Company: Tech Inc.", "user": { "id": "73ced5dd-8bfb-43e0-982b-4b8bb1382c91", "email": "ida.fossdal@godmat.no" } } ], "actor": { "type": "user", "id": "a558d25a-99d6-45b1-855c-63b052adc5f3", "email": "admin@company.com", "name": "Karina Strand" } }
Supported fields
| Field | JSON key |
|---|---|
| Position ID | id |
| Job title | title |
| Job description | description — either a custom description, or the description defined on the job title entity |
| Contract type | contractType — either null or one of: Permanent, Temporary, Seasonal, Casual, Trainee, Job training program, Consultant, Freelance, Third party, Other |
| Percentage | percentage |
| Contract country | contractCountry |
| Note | note |
| Contract start date | contractStartDate |
| First day of work | firstDayOfWork |
| Probation end date | probationEndDate |
| Termination notice date | terminationNoticeDate |
| Last day of work | lastDayOfWork |
| Contract end date | contractEndDate |
| End reason | endReason — either null or one of: The employer has terminated the employee, The employee has resigned, The contract, engagement, or temporary position has expired, Changed payroll system or accountant, Other |
| Wanted termination | wantedEnd — true or false |
| End note | endNote |
Absence events
For absence data, the topic will be one of:
absence-createabsence-updateabsence-deleteabsence-export
All absence event request bodies include an absence array. Each object contains all supported fields for the entry.
Create
Sent as a POST request.
Example:
{ "topic": "absence-create", "absence": [ { "id": "45be6bce-88f5-4f67-a6de-cbd84df21e1e", "status": "pending", "type": { "name": "vacation" }, "user": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" }, "startDate": "2022-10-24", "endDate": "2022-10-28", "grade": 1, "workRelated": false, "note": null, "comment": null, "reviewedAt": null, "reviewedBy": null, "registeredAt": "2022-10-04T11:57:13.819Z", "registeredBy": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" } } ], "actor": { "type": "user", "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no", "name": "Patrick Remen" } }
Update
Sent as a PATCH request. Triggered when a reviewer changes the status, an employee edits their request, or a manager makes changes on behalf of an employee.
Example:
{ "topic": "absence-update", "absence": [ { "id": "e9b5a57f-2f36-462e-aed2-19c6aa990fce", "status": "approved", "type": { "name": "vacation" }, "user": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" }, "startDate": "2022-10-10", "endDate": "2022-10-14", "grade": 1, "workRelated": false, "note": null, "comment": null, "reviewedAt": "2022-10-05T11:57:13.819Z", "reviewedBy": { "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no" }, "registeredAt": "2022-10-04T11:51:08.962Z", "registeredBy": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" } } ], "actor": { "type": "user", "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "name": "Jesper Blom" } }
Delete
Sent as a DELETE request. The absence objects contain the ID only.
Example:
{ "topic": "absence-delete", "absence": [ { "id": "e9b5a57f-2f36-462e-aed2-19c6aa990fce" } ], "actor": { "type": "user", "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "name": "Jesper Blom" } }
Export
When you activate an export from the webhook integration page, you select a time period, which users to include, and which absence types to include. The result is sent as one or more POST requests, each containing up to 50 entries.
Example:
{ "topic": "absence-export", "exportFrom": "2022-10-01", "exportTo": "2022-12-31", "absence": [ { "id": "e9b5a57f-2f36-462e-aed2-19c6aa990fce", "status": "approved", "type": { "name": "vacation" }, "user": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" }, "startDate": "2022-10-10", "endDate": "2022-10-14", "grade": 1, "workRelated": false, "note": null, "comment": null, "reviewedAt": "2022-10-05T11:57:13.819Z", "reviewedBy": { "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no" }, "registeredAt": "2022-10-04T11:51:08.962Z", "registeredBy": { "id": "c2c8d497-352a-4c21-bf7c-aefbaec37717", "email": "patrick.remen@godmat.no" } } ], "actor": { "type": "user", "id": "d96434d7-9506-4d5f-a969-7d1eea8bc3d6", "email": "jesper.blom@godmat.no", "name": "Jesper Blom" } }
Supported fields
| Field | JSON key |
|---|---|
| Entry ID | id |
| Status | status — one of pending, approved, or rejected |
| Type | type — an object containing name, which is one of: vacation, selfCertifiedSick, sick, paidTimeOff, unpaidTimeOff, sickChild, parentalLeave |
| User | user — an object containing id and email |
| Start date | startDate |
| End date | endDate |
| Grade | grade — a decimal number, where 1.0 is 100% absence, 0.5 is 50%, etc. |
| Work related | workRelated |
| Note | note — added when the entry was registered |
| Comment | comment — added when the entry was reviewed |
| Reviewed at | reviewedAt |
| Reviewed by | reviewedBy — an object containing id and email |
| Registered at | registeredAt |
| Registered by | registeredBy — an object containing id and email |
Company, Team & Location events
For Company, Team, and Location data, the topic will be one of:
groups-creategroups-updategroups-deletegroups-exportgroups-add-membersgroups-remove-members
All event bodies include a groups array. Each group object always contains id, name, and type. The type value will be COMPANY, TEAM, or LOCATION. Company groups may also include organizationNumber.
Create
Sent as a POST request.
Example:
{ "topic": "groups-create", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>", "description": "<optional group description>", "organizationNumber": "<organization number, Company only>", "links": [ "<link name>\n<link url>" ], "address": { "line1": "Example street 57", "line2": "<optional second line>", "postalCode": "9999", "city": "Demo city", "country": "Theoretia" }, "members": [ { "id": "<user ID>", "email": "<user email>" } ] } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Update
Sent as a PUT request.
Example:
{ "topic": "groups-update", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>", "description": "<optional group description>", "organizationNumber": "<organization number, Company only>", "links": [ "<link name>\n<link url>" ], "address": { "line1": "Example street 57", "line2": "<optional second line>", "postalCode": "9999", "city": "Demo city", "country": "Theoretia" } } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Delete
Sent as a DELETE request. Group objects contain id, name, and type only.
Example:
{ "topic": "groups-delete", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>" } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Export
When you activate an export from the webhook integration page, you select which group types to include. The result is sent as one or more POST requests, each containing up to 50 group objects.
Example:
{ "topic": "groups-export", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>", "description": "<optional group description>", "organizationNumber": "<organization number, Company only>", "links": [ "<link name>\n<link url>" ], "address": { "line1": "Example street 57", "line2": "<optional second line>", "postalCode": "9999", "city": "Demo city", "country": "Theoretia" }, "members": [ { "id": "<user ID>", "email": "<user email>" } ] } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Add members
Sent as a PUT request.
Example:
{ "topic": "groups-add-members", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>", "members-added": [ { "id": "<user ID>", "email": "<user email>" } ] } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Remove members
Sent as a PUT request.
Example:
{ "topic": "groups-remove-members", "groups": [ { "id": "<group ID>", "name": "<group name>", "type": "<COMPANY/TEAM/LOCATION>", "members-removed": [ { "id": "<user ID>", "email": "<user email>" } ] } ], "actor": { "type": "user", "id": "<user ID>", "email": "<user email>", "name": "<user name>" } }
Event actor
Every event body includes an actor object describing who triggered the event. For create, update, and delete events, this is the user who made the change. For export events, it is the user who initiated the export.
💡 Some actions are triggered by integrations rather than users. In that case, the actor object will have type set to integration.
| Field | JSON key | Notes |
|---|---|---|
| Actor ID | id |
Always present. A stable UUID. For integration actors, the ID remains the same after disconnect and reconnect, but changes if the connection is removed and recreated. |
| Actor type | type |
Always present. Either user or integration. |
email |
Present when type is user. Unique at the time of the request, but can change if the user's email is updated. |
|
| Name | name |
The name of the actor. |
Security & compliance
By connecting via webhooks, you authorise Huma to access your webhook endpoint as described in our Terms of Service under Third-Party Platforms.
FAQ
What happens if my endpoint doesn't respond in time?
Huma expects a response within 1 second. If your endpoint does not respond in time, the request will time out. We recommend acknowledging the request immediately and processing the data asynchronously.
Why am I receiving a users-update event when a position changes?
When a position change affects the employee's current primary position, Huma also sends a users-update event to keep the deprecated position-related fields on the user profile in sync. This is expected behaviour.
Can I use the same webhook URL for multiple event types?
Yes. You can configure a single webhook connection to receive any combination of profile, position, absence, and group events. Use the topic field in each request to identify the event type.
What does the grade field in absence events mean?
grade is a decimal representing the degree of absence. A value of 1.0 means the employee is fully absent, 0.5 means 50%, and so on.
How do I verify that a request actually comes from Huma?
Set a client secret when configuring your webhook. Each request will then include a huma-hmac-sha256 header containing a hash of the request body signed with your secret. Verify this hash on your end to confirm the request is genuine.
What is the employmentId and can it change?
employmentId is a unique identifier you assign to employees in Huma. It is unique at the time the request is sent, but it can be changed or removed by a user with the appropriate permissions. Do not assume it is permanently stable across all future requests.
Is the Export code from an absence policy included in webhook payloads?
No. The Export code is a configuration field on the absence type policy in Huma, and is not included in webhook payloads. Absence events contain the absence type name (e.g. vacation, sick) but not any policy-level configuration such as the Export code.