Event reference
Every webhook delivery shares the same envelope:
{
"id": "8e3f5c44-1cd1-4a40-bb4f-9d4f95c54c1d",
"type": "job.created",
"createdAt": 1715520000,
"subscriptionId": "7c2f8f7a-d8ab-4d4a-9a09-71b4e6dffb8a",
"deliveryAttempt": 1,
"data": { /* type-specific */ }
}
| Envelope field | Type | Description |
|---|---|---|
id | string (uuid) | Unique event ID. Mirror to Jobven-Event-Id header. Use for dedupe. |
type | string | Event type. See the sections below. |
createdAt | integer (unix seconds) | When the event was created on Jobven's side. |
subscriptionId | string (uuid) | Your subscription ID. |
deliveryAttempt | integer | 1 on the first attempt, increments for retries. |
data | object | Type-specific payload (documented below). |
job.created
A job that matches your filter was listed: it is new, or a closed job reopened. Its listedAt is the moment it was listed, the value the Pull API's listedAfter filters on.
{
"type": "job.created",
"data": {
"job": { /* full Job object, see /docs/reference/jobs */ }
}
}
The data.job shape matches the public Pull API Job object exactly. See Jobs API reference.
job.created can fire for a job you have already seen. If an employer relists a role we
previously closed, it returns under the same id and we send job.created again rather than
job.updated: you were told the job closed, so there is nothing to apply an update to.
Handle this event as an upsert keyed on id, not as an insert.job.updated
A job we've previously delivered to you changed. Only fires when at least one tracked field actually changed: title, description, salary, employmentType, remoteType, applyUrl or expiresAt.
A change of status does not produce job.updated. Status movements have their own events: see job.closed below, and the reopen note under job.created.
A closed job never receives job.updated, whatever changes on it: you were told it closed, and it has no further events unless the employer relists it, which arrives as job.created.
{
"type": "job.updated",
"data": {
"job": { /* full Job object, current values */ },
"changedFields": ["title", "salaryMax"]
}
}
data field | Type | Description |
|---|---|---|
job | object | Current state of the job (full Job object). |
changedFields | array of string | Names of fields that changed in this update. Useful for incremental sync. |
job.closed
The employer's job board stopped listing the role. A job is closed once it has been absent from the employer's board for 30 days, so this event arrives about 30 days after the role was last listed. The payload is minimal, the same shape as job.deleted, because you already hold the record: look it up by id and mark it closed. If you need the job's other fields, fetch GET /v1/public/jobs/{id}.
Closures are free: they do not count against your monthly job allowance, and they keep arriving even when your account is over its limit, so the jobs you hold do not go stale.
job.closed ignores your subscription's filter, like job.deleted: a job can reach you under one set of attributes and close under another, so every subscription receives every closure. Match it on id, not applyUrl: two different jobs can share an apply URL, and a closure for one must not close the other. If you don't recognise the id, ignore it.
closed means the role is no longer being advertised. It is not used for records we withdraw for our own reasons, such as a listing we could no longer verify; those are removed with job.deleted instead.
A closed job is not necessarily final. If the employer relists it, you will receive job.created for the same id.
{
"type": "job.closed",
"data": {
"job": {
"id": "01HZX7K4VWJ8MMGS31F7DCYJ8Z",
"applyUrl": "https://example.com/jobs/123",
"closedAt": 1715520000
}
}
}
data.job field | Type | Description |
|---|---|---|
id | string | Job ID. Use this to find the record in your store. |
applyUrl | string | The job's apply URL. Useful as a secondary key if you stored that. |
closedAt | integer (unix seconds) | When Jobven closed the job: the same value as closedAt on the job in the Pull API. About 30 days after the role was last listed. |
job.deleted
A job was removed from Jobven entirely. Your store should delete the corresponding record. The data.job payload is intentionally minimal: by the time you receive this event, the full record may already be gone from our database.
This event ignores your subscription's filter: you get it for every deletion, including jobs that never matched. See Filter scope. If you don't recognise the id or applyUrl, ignore it.
{
"type": "job.deleted",
"data": {
"job": {
"id": "01HZX7K4VWJ8MMGS31F7DCYJ8Z",
"applyUrl": "https://example.com/jobs/123",
"deletedAt": 1715520000
}
}
}
data.job field | Type | Description |
|---|---|---|
id | string | Job ID. Use this to find and delete from your store. |
applyUrl | string | The job's apply URL. Useful as a secondary key if you stored that. |
deletedAt | integer (unix seconds) | When the deletion occurred. |
webhook.test
Fired when you click Send test in the dashboard. Deterministic content: the same fixture every time, regardless of your subscription's filter or event-type selection. Test events:
- ignore your subscription's
eventsandfiltersettings - don't count against your monthly job allowance
- are terminal on first failure (no retries)
{
"type": "webhook.test",
"data": {
"job": {
"id": "job_test_fixture",
"title": "Senior TypeScript Engineer (Test Event)",
"companies": [
{ "name": "Jobven Test Co", "website": "https://jobven.example.com" }
]
}
}
}
Use webhook.test events to wire up and verify your signature handler before live events start landing.