A webhook is an automated method of communication between two applications. It enables real-time data delivery from one application to another by triggering an HTTP POST request to a specified URL. Webhooks are commonly used for instant notifications and data synchronization between different systems.
The HRMS application leverages webhooks to notify external systems, about employee-related events such as creation, updates, or deactivation; milestone events such as birthdays and work anniversaries; and leave request events such as creation, decline, approval, status updates, recall, cancellation, and reset. The HRMS application sends these notifications to a designated webhook URL provided.
Supported events
Employee events are categorised into the following types, each sent alongside the payload:
{
"event": "create_employee",
"data": {
"title": "Mr",
"first_name": "Demouuu",
"last_name": "Account",
"other_names": "ANother",
"email": "[email protected]",
"phone": "07057646553",
"employee_code": "SHR-DOT82833",
"gender": "Male",
"age": 40,
"date_of_birth": "1984-03-07T23:00:00.000000Z",
"marital_status": "Single",
"employment_date": "2024-02-29T23:00:00.000000Z",
"job_role": "Admin associate",
"department": null,
"paygroup": "Junior Level",
"paygrade": "Junior Associate I",
"line_manager": "********* *********",
"cost_center": "All",
"branch": "Ajah",
"region": "All",
"unit": "Admin",
"state_of_origin": null,
"residential_address": null,
"permanent_address": null,
"exit_date": null,
"confirmation_status": "Unconfirmed",
"contract_type": "Full time",
"contract_start_date": "2024-03-01",
"entity": "Seamless-Tech"
}
} {
"event": "update_employee",
"data": {
"title": "Mr",
"first_name": "Demouuu",
"last_name": "Account",
"other_names": "ANother",
"email": "[email protected]",
"phone": "07057646553",
"employee_code": "SHR-DOT82833",
"gender": "Male",
"age": 40,
"date_of_birth": "1984-03-07T23:00:00.000000Z",
"marital_status": "Single",
"employment_date": "2024-02-29T23:00:00.000000Z",
"job_role": "Admin associate",
"department": null,
"paygroup": "Junior Level",
"paygrade": "Junior Associate I",
"line_manager": "********* *********",
"cost_center": "All",
"branch": "Ajah",
"region": "All",
"unit": "Admin",
"state_of_origin": null,
"residential_address": null,
"permanent_address": null,
"exit_date": null,
"confirmation_status": "Unconfirmed",
"contract_type": "Full time",
"contract_start_date": "2024-03-01",
"entity": "Seamless-Tech"
}
} {
"event": "deactivate_employee",
"data": {
"title": "Mr",
"first_name": "Demouuu",
"last_name": "Account",
"other_names": "ANother",
"email": "[email protected]",
"phone": "07057646553",
"employee_code": "SHR-DOT82833",
"gender": "Male",
"age": 40,
"date_of_birth": "1984-03-07T23:00:00.000000Z",
"marital_status": "Single",
"employment_date": "2024-02-29T23:00:00.000000Z",
"job_role": "Admin associate",
"department": null,
"paygroup": "Junior Level",
"paygrade": "Junior Associate I",
"line_manager": "********* *********",
"cost_center": "All",
"branch": "Ajah",
"region": "All",
"unit": "Admin",
"state_of_origin": null,
"residential_address": null,
"permanent_address": null,
"exit_date": "2024-01-30",
"confirmation_status": "Unconfirmed",
"contract_type": "Full time",
"contract_start_date": "2024-03-01",
"entity": "Seamless-Tech"
}
} {
"event": "work_anniversary",
"data": {
"employee_code": "Bee_0784",
"first_name": "Test",
"last_name": "Deo",
"avatar": null,
"years_in_organization": 2,
"date_joined": "2024-07-31"
}
}{
"event": "birthday",
"data": {
"employee_code": "tee_Sync",
"first_name": "Jmae",
"last_name": "Test123",
"avatar": null,
"date_of_birth": "2000-07-31"
}
}{
event: 'leave_request_created',
processor: 'custom',
data: {
id: 21383,
requester: {
id: 182,
employee_code: 'FPL-112',
first_name: 'Test Grace',
last_name: 'Gracious',
avatar: null,
gender: 'Female',
email: '[email protected]'
},
period: {
start: '2026-08-15T07:00:00.000000Z',
end: '2026-08-16T16:00:00.000000Z',
duration: [Object]
},
resumption_date: '2026-08-17T07:00:00.000000Z',
date_applied: '2026-08-18T15:24:50.000000Z',
policy: { id: 2, name: 'Annual Leave' },
status: {
label: 'Pending',
detail: '',
has_detail: false,
relief_officer: 'Pending'
},
relief_officer: {
id: 355,
employee_code: 'FPL-115',
first_name: 'TESTT',
last_name: 'MAV',
avatar: null,
gender: 'Female',
email: '[email protected]'
},
decline_reason: null,
handover_note: { type: 'text', value: null, link: null },
proof: null
}
}{
"event": "leave_request_approved",
"data": {
"id": 68,
"requester": {
"id": 802,
"employee_code": "EMP-001",
"first_name": "Leave",
"last_name": "User",
"avatar": null,
"gender": "Female",
"email": null
},
"period": {
"start": "2026-07-29T23:00:00.000000Z",
"end": "2026-07-29T23:00:00.000000Z",
"duration": {}
},
"resumption_date": "2026-07-30T23:00:00.000000Z",
"date_applied": "2026-07-30T11:51:10.000000Z",
"policy": {
"id": 2,
"name": "Annual Leave"
},
"status": {
"label": "Approved",
"detail": "APPROVED",
"has_detail": false,
"relief_officer": "Approved"
},
"relief_officer": {
"id": 6,
"employee_code": "TestA_SSO",
"first_name": "Test Blessing",
"last_name": "Test One",
"avatar": null,
"gender": "Female",
"email": "[email protected]"
},
"decline_reason": null,
"handover_note": {
"type": "file",
"value": null,
"link": null
},
"proof": null
}
}{
event: 'leave_request_declined',
processor: 'custom',
data: {
id: 20799,
requester: {
id: 111,
employee_code: 'CPL-312',
first_name: 'Test Ade',
last_name: 'Test Dayo',
avatar: null,
gender: 'Male',
email: '[email protected]'
},
period: {
start: '2026-08-18T11:00:00.000000Z',
end: '2026-08-25T10:00:00.000000Z',
duration: [Object]
},
resumption_date: '2026-08-25T10:00:00.000000Z',
date_applied: '2026-08-11T07:20:40.000000Z',
policy: { id: 2, name: 'Annual Leave' },
status: {
label: 'Declined',
detail: 'DECLINED',
has_detail: false,
relief_officer: 'Approved'
},
relief_officer: {
id: 394,
employee_code: 'APL-873',
first_name: 'Test Ada',
last_name: 'Test Eze',
avatar: null,
gender: 'Female',
email: '[email protected]'
}
}{
event: 'leave_request_status_updated',
processor: 'custom',
data: {
id: 20799,
requester: {
id: 111,
employee_code: 'AFL-712',
first_name: 'TestB',
last_name: 'TestA',
avatar: null,
gender: 'Male',
email: '[email protected]'
},
period: {
start: '2026-08-18T11:00:00.000000Z',
end: '2026-08-25T10:00:00.000000Z',
duration: [Object]
},
resumption_date: '2026-08-25T10:00:00.000000Z',
date_applied: '2026-08-11T07:20:40.000000Z',
policy: { id: 2, name: 'Annual Leave' },
status: {
label: 'Declined',
detail: 'DECLINED',
has_detail: false,
relief_officer: 'Approved'
},
relief_officer: {
id: 394,
employee_code: 'FPL-873',
first_name: 'TestC',
last_name: 'Test2',
avatar: null,
gender: 'Female',
email: '[email protected]'
},
decline_reason: 'Needs to improve his MTD performance for the month before going for any leave.',
handover_note: { type: 'text', value: null, link: null },
proof: null
}
}
{
"event": "leave_request_recalled",
"processor": "custom",
"data": {
"id": 20487,
"requester": {
"id": 78,
"employee_code": "APL-405",
"first_name": "PTest",
"last_name": "KTest",
"avatar": null,
"gender": "Male",
"email": "[email protected]"
},
"period": {
"start": "2026-02-21T23:00:00.000000Z",
"end": "2026-02-22T23:00:00.000000Z",
"duration": {
"label": "2 Days",
"value": "2.00"
}
},
"resumption_date": "2026-02-25T23:00:00.000000Z",
"date_applied": "2026-02-07T02:33:34.000000Z",
"policy": {
"id": 1,
"name": "Annual Leave"
},
"status": {
"label": "Recalled",
"detail": "RECALLED",
"has_detail": false,
"relief_officer": "Recalled"
},
"relief_officer": {
"id": 4,
"employee_code": "EMPLOYEE002",
"first_name": "DTest",
"last_name": "STest",
"avatar": null,
"gender": "Male",
"email": "[email protected]"
},
"decline_reason": null,
"handover_note": {
"type": "file",
"value": "https://demo.seamlesshrm.com/api/v1/leave/assets/code_1707273211.png",
"link": "https://demo.seamlesshrm.com/api/v1/leave/assets/code_1707273211.png"
}
}
}
I'd treat status.label, status.detail, and status.relief_officer here as best-guess placeholders, not confirmed values — please swap in the actual fired payload for a recall event if you{
event: 'leave_request_cancelled',
processor: 'custom',
data: {
id: 20487,
requester: {
id: 78,
employee_code: 'APL-405',
first_name: 'ZTest',
last_name: 'NTest',
avatar: null,
gender: 'Female',
email: '[email protected]'
},
period: {
start: '2027-07-27T08:00:00.000000Z',
end: '2027-07-28T08:00:00.000000Z',
duration: [Object]
},
resumption_date: '2027-07-28T08:00:00.000000Z',
date_applied: '2026-08-06T09:03:20.000000Z',
policy: { id: 2, name: 'Annual Leave' },
status: {
label: 'Cancelled',
detail: 'CANCELLED',
has_detail: false,
relief_officer: 'Cancelled'
},
relief_officer: {
id: 11,
employee_code: 'APL-587',
first_name: 'CTest',
last_name: 'MTest',
avatar: null,
gender: 'Female',
email: '[email protected]'
}
} {
event: 'leave_request_reset',
processor: 'custom',
data: {
id: 2685,
requester: {
id: 54,
employee_code: 'APL-583',
first_name: 'OTest',
last_name: 'MTest',
avatar: null,
gender: 'Male',
email: '[email protected]'
},
period: {
start: '2026-02-22T23:00:00.000000Z',
end: '2026-03-02T23:00:00.000000Z',
duration: [Object]
},
resumption_date: '2026-03-03T23:00:00.000000Z',
date_applied: '2026-02-10T12:37:38.000000Z',
policy: { id: 2, name: 'Annual Leave' },
status: {
label: 'Reset',
detail: 'RESET',
has_detail: false,
relief_officer: 'Approved'
},
relief_officer: {
id: 46,
employee_code: 'APL-669',
first_name: 'LTest',
last_name: 'KTest',
avatar: null,
gender: 'Male',
email: '[email protected]'
},
decline_reason: null,
handover_note: { type: 'text', value: null, link: null },
proof: null
}
}
Webhook Signature Validation
SeamlessHR events include the "x-seamlesshr-signature" header. The value of this header is an HMAC SHA512 signature of the event payload signed using your secret key. It is required to verify the header signature before processing the event:
const crypto = require('crypto');
const secret = process.env.SECRET_KEY;
// Using Express
app.post("/webhook/url", function(req, res) {
// verify signature
const hash = crypto.createHmac('sha512', secret).update(JSON.stringify(req.body)).digest('hex');
if (hash == req.headers['x-seamlesshr-signature']) {
// Retrieve the request's body
const event = req.body;
// Do something with event
}
res.send(200);
});<?php
// only a post with paystack signature header gets our attention
if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST' ) || !array_key_exists('HTTP_X_SEAMLESSHR_SIGNATURE', $_SERVER) )
exit();
// Retrieve the request's body
$input = @file_get_contents("php://input");
define('SEAMLESSHR_SECRET_KEY','SECRET_KEY');
// verify signature
if($_SERVER['HTTP_X_SEAMLESSHR_SIGNATURE'] !== hash_hmac('sha512', $input, SEAMLESSHR_SECRET_KEY))
exit();
http_response_code(200);
// parse event (which is json string) as object
// Do something - that will not take long - with $event
$event = json_decode($input);
exit();
?>