Events

The eleven webhook event types, the envelope every event uses, and what each payload contains.

Every webhook Cavuno sends uses the same JSON envelope. What changes between events is the type field and the object inside data.

Event types

EventFires when
job.createdA job is added to your board.
job.updatedA job changes in a way visible to operators.
job.deletedA job is removed.
company.createdA company is added to your board.
company.updatedAn allowlisted company field changes.
company.deletedA company is removed.
candidate.createdA board user becomes a candidate you can see.
candidate.updatedA candidate's email address or display name changes.
candidate.deletedA candidate's data has been permanently erased.
marketing_permission.grantedSomeone agreed to receive marketing email from you.
marketing_permission.withdrawnSomeone no longer consents, or their account was deleted.

There is no job.published or job.expired event. Publishing, pausing, and expiring a job all arrive as job.updated. Read status in the snapshot and changed_fields in the payload to see what happened.

Two other details are worth knowing. candidate.deleted fires only once erasure has finished, not when a deletion is requested. Re-saving a record without changing anything produces no event at all.

The two marketing events describe consent, which lives on the board user record — both candidates and employers can hold it. Wire them into your newsletter tool: add the person on granted, remove them on withdrawn. A withdrawal carries a reason; account_deleted means the person's account is gone, so delete them from your own systems too. Someone who never decided produces no event and appears in no list — absence means no consent, never a default.

The event envelope

json
{
"id": "evt_01EXAMPLEJOBUPDATED",
"object": "event",
"type": "job.updated",
"schema_version": "1",
"occurred_at": "2026-07-24T13:00:00Z",
"board_id": "acc_01EXAMPLEBOARD",
"data": {
"object": {
"id": "jobs_01EXAMPLEJOB",
"object": "job",
"slug": "senior-engineer",
"title": "Staff Engineer",
"status": "published",
"company_id": "companies_01EXAMPLE",
"company_name": "Acme",
"location": "Remote",
"employment_type": "full_time",
"workplace_type": "remote",
"published_at": "2026-07-24T12:00:00Z",
"expires_at": "2026-08-23T12:00:00Z",
"updated_at": "2026-07-24T13:00:00Z",
"revision": 2
},
"changed_fields": ["title"]
}
}

Use id to recognise an event you have already handled. It stays the same across retries and replays. Use revision to reject an older event that arrives after a newer one. occurred_at tells you when the change happened, but it is not an ordering guarantee.

What each snapshot contains

Payloads are built from a fixed list of fields. Nothing else is included, even if the record holds more.

ObjectFields
Jobid, object, slug, title, status, company_id, company_name, location, employment_type, workplace_type, published_at, expires_at, updated_at, revision
Companyid, object, slug, name, website, logo_url, updated_at, revision
Candidateid, object, email, display_name, created_at, updated_at, revision
Marketing permissionid, object, email, display_name, role, status, source, reason, granted_at, withdrawn_at, updated_at, revision

Companies carry no location field, because the company record has no canonical public location. logo_url can be null for older logos.

On a marketing permission, id is the board user's id, source names the surface where the decision was made (for example candidate_sign_up or notification_preferences), and reason is set only on withdrawals: person_request, operator_request, or account_deleted. The wording the person agreed to is not included — it belongs to your board's pages, and the record stores the decision, not the prose.

Changed fields

On an update, changed_fields lists which of a fixed set of fields changed. It is always empty on create and delete events.

ObjectFields that can appear
Jobslug, title, status, company_id, company_name, location, employment_type, workplace_type, published_at, expires_at
Companyslug, name, website, logo_url
Candidateemail, display_name
Marketing permissionstatus, source, reason, granted_at, withdrawn_at

A change to a field outside this list does not appear in changed_fields, and for candidates it does not produce an event. Editing a candidate's bio, for example, changes nothing on the wire.

Deleted events

A delete event carries a tombstone instead of a snapshot. It holds only what you need to remove your own copy.

json
{
"id": "evt_01EXAMPLEJOBDELETED",
"object": "event",
"type": "job.deleted",
"schema_version": "1",
"occurred_at": "2026-07-24T14:00:00Z",
"board_id": "acc_01EXAMPLEBOARD",
"data": {
"object": {
"id": "jobs_01EXAMPLEJOB",
"object": "job",
"revision": 3,
"deleted": true,
"deleted_at": "2026-07-24T14:00:00Z",
"slug": "senior-engineer",
"company_id": "companies_01EXAMPLE"
},
"changed_fields": []
}
}

Treat the tombstone as final. The object no longer exists, so reading it back from the API will not work.

Schema versions

Every event carries schema_version. Fields may be added to an existing version, so ignore any field you do not recognise. A change that would break existing receivers ships as a new version instead.

Next steps

Check the signature on every request before you act on it. See Verifying signatures.