API documentation
ATS / HRMS Job Webhook API
A single webhook endpoint that keeps job postings in sync between an external ATS or HRMS and the platform. Whenever a job is created, updated, or closed on your side, send us an event — we validate it, resolve the company record, and write the job through.
Before sharing externally: replace every <PLACEHOLDER> in this document — base URL, environment, webhook secret header name, and secret value — with your real integration details. See Integration details.
02 · Authentication
Authentication
Every request is checked by webhook-secret middleware. Send the shared secret in the agreed request header on every call — there is no OAuth flow, no query-string token, and no session.
Content-Type: application/json <WEBHOOK_SECRET_HEADER>: <WEBHOOK_SECRET>
Security note — never place the webhook secret inside the JSON body, and never log it or echo it back in a response.
03 · Base URL & environment
Base URL & environment
All paths in this document are relative to the base URL of the environment you're targeting.
| Environment | Base URL |
|---|---|
| Staging | POST <STAGING_BASE_URL>/ats/jobs |
| Production | TBD |
04 · Endpoint reference
Endpoint reference
/ats/jobs
| Method | POST |
| Endpoint | <BASE_URL>/ats/jobs |
| Content-Type | application/json |
One endpoint handles all three lifecycle events — the event field in the request body tells the API which operation to run.
05 · Supported events
Supported events
| Event | Purpose |
|---|---|
job.created | Create a new job. If job_id already exists, the existing job is updated instead. |
job.updated | Update an existing job. Partial job fields are supported. |
job.closed | Close an existing job by setting status to false. |
06 · Request structure
Request structure
Standard envelope
Every request — regardless of event — uses the same top-level shape.
{
"event": "job.created",
"payload": {
"job": {},
"company": {}
}
}
| Field | Type | Required | Description |
|---|---|---|---|
event | String | Yes | One of job.created, job.updated, job.closed |
payload | Object | Yes | Container for job and company data |
payload.job | Object | Yes | Job information |
payload.company | Object | For create | Company information; optional for updates |
Job object fields
| Field | Type | Required (create) | Max length | Description |
|---|---|---|---|---|
job_id | String | Yes | 50 | Unique external ATS/HRMS job ID |
title | String | Yes | 225 | Job title |
description | String | Yes | — | Job description |
city | String | Yes | 50 | Job location city |
state | String | Yes | 50 | Job location state |
status | Boolean * | Yes | — | Job open / closed state |
0/1 and selected string equivalents — see Job status values.Company object fields
| Field | Type | Required (create) | Max length | Description |
|---|---|---|---|---|
name | String | Yes | 115 | Company name |
email | String | Yes | 115 | Company email; used to find an existing company |
logo | String | Yes | — | Company logo URL or stored value |
type | String | Yes | — | Company / industry type |
city | String | Yes | 50 | Company city |
state | String | Yes | 50 | Company state |
Company matching — companies are looked up by email. A match updates the existing record; no match creates one.
07 · Event guides
Event guides
Runnable requests for each lifecycle event. Pick a language once — every example on this page switches with you.
job.created
Use this when a new job is created in your ATS/HRMS. All documented job and company fields are required.
Idempotent — if job_id already exists, this updates that job instead of creating a duplicate.
job.updated
job_id is always required. Send only the fields that changed — if company data is included, email is required with it.
Update validation — at least one job field besides job_id, or company data, must be present.
job.closed
Only job_id is required. The API sets status to false automatically.
08 · Job status values
Job status values
| Input | Meaning |
|---|---|
true / 1 / "true" / "1" / "active" / "open" | Open / active |
false / 0 / "false" / "0" / "inactive" / "closed" | Closed / inactive |
For third-party integrations, prefer sending native JSON booleans (true / false) whenever possible.
09 · Response reference
Response reference
Success response
{
"success": true,
"message": "Webhook processed successfully",
"data": {
"event": "job.created",
"action": "created_job",
"job_id": "JOB-1001",
"id": 101,
"company_id": 25
}
}
Response action values
| Action | Meaning |
|---|---|
created_job | New job created |
updated_existing_job | Existing job updated from a repeated job.created event |
updated_job | Existing job updated |
closed_job | Existing job closed |
10 · Validation rules
Validation rules
- —Request body must be a valid JSON object.
- —
eventis required and must be one of the supported events. - —Unsupported job or company fields are rejected.
- —All required fields must be present for
job.created. - —
job_idis required forjob.updatedandjob.closed. - —Company email must be a valid address whenever provided.
- —Configured maximum field lengths are enforced.
- —
job.updatedmust include at least one field to change. - —
job.updatedandjob.closedreturn"Job not found"whenjob_iddoesn't exist.
11 · Error handling
Error handling
Validation, not-found, duplicate, relationship, and database errors are categorized internally. The API never exposes ORM internals, SQL, stack traces, or secrets in its response.
| Scenario | Status | Example message |
|---|---|---|
| Invalid / missing field | 400 | Invalid webhook payload / field-specific message |
| Unsupported event | 400 | Unsupported webhook event |
| Job not found | 404 | Job not found |
| Duplicate / unique conflict | 409 | Webhook record already exists |
| Database / internal failure | 500 | Failed to process webhook |
Implementation note — the current controller returns HTTP 500 for every service error. Before this API is shared externally, the controller should return error.statusCode when available, so integrators can tell validation errors apart from transient server errors.
12 · Retry & duplicate delivery
Retry & duplicate delivery
Network errors and timeouts can cause a retry. Reuse the same job_id across a job's full lifecycle so retries and later events resolve to the same record.
Create: JOB-1001 Update: JOB-1001 Close: JOB-1001
A repeated job.created delivery with the same job_id updates the existing job — it never creates a duplicate row.
13 · Integration flow
Integration flow
-
1
ATS / HRMS sends
POST to
/ats/jobs -
2
Verify secret
Webhook-secret header checked
-
3
Validate
Event type + payload shape
-
4
Create / find company
Matched by company email
-
5
Create / update / close job
Resolved by
job_id -
6
JSON response
Acknowledged with 2xx
14 · Integration checklist
Integration checklist
- Use
POSTfor all webhook events. - Send
Content-Type: application/json. - Include the webhook secret header on every request.
- Use only
job.created,job.updated, orjob.closed. - Use a permanent, unique
job_idper job. - Reuse that same
job_idfor create, update, and close. - Send every required field for
job.created. - Send only changed fields for
job.updatedwhen appropriate. - Send
job.closedthe moment a job goes inactive. - Don't send undocumented fields — they're rejected.
- Prefer JSON booleans (
true/false) forstatus. - Treat any 2xx response as acknowledgement.
15 · Integration details
Integration details to complete before sharing
| Item | Value |
|---|---|
| Staging base URL | <STAGING_BASE_URL> |
| Production base URL | <PRODUCTION_BASE_URL> |
| Webhook endpoint | /ats/jobs |
| Authentication header | <WEBHOOK_SECRET_HEADER> |
| Authentication secret | Share securely — do not commit to this document if it will be widely distributed |
| Technical contact | <CONTACT_NAME / EMAIL> |
16 · Support
Support
For integration support, payload clarification, staging access, authentication credentials, or production rollout questions, contact the designated development / integration team.