Events
The eleven webhook event types, the envelope every event uses, and what each payload contains.
A
JEvery webhook Cavuno sends uses the same JSON envelope. What changes between events is the type field and the object inside data.
Event types
| Event | Fires when |
|---|---|
job.created | A job is added to your board. |
job.updated | A job changes in a way visible to operators. |
job.deleted | A job is removed. |
company.created | A company is added to your board. |
company.updated | An allowlisted company field changes. |
company.deleted | A company is removed. |
candidate.created | A board user becomes a candidate you can see. |
candidate.updated | A candidate's email address or display name changes. |
candidate.deleted | A candidate's data has been permanently erased. |
marketing_permission.granted | Someone agreed to receive marketing email from you. |
marketing_permission.withdrawn | Someone 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
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.
| Object | Fields |
|---|---|
| Job | id, object, slug, title, status, company_id, company_name, location, employment_type, workplace_type, published_at, expires_at, updated_at, revision |
| Company | id, object, slug, name, website, logo_url, updated_at, revision |
| Candidate | id, object, email, display_name, created_at, updated_at, revision |
| Marketing permission | id, 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.
| Object | Fields that can appear |
|---|---|
| Job | slug, title, status, company_id, company_name, location, employment_type, workplace_type, published_at, expires_at |
| Company | slug, name, website, logo_url |
| Candidate | email, display_name |
| Marketing permission | status, 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.
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.