Skip to content
API v1 preview — endpoints and fields may change before general availability.

Events

Which events can I receive, and what should my integration do with each one?

View .md
Cost: 1 credit / job.opened deliveredFree if you already paid for that job. Every other event type, retries and replays are free.

An event tells you that something changed: a job opened, closed or was re-listed, or a company started or stopped hiring. Events come from two places:

  • Watches. Watch a company domain (type: company) or a saved search (type: search) with POST /v1/watches.
  • Async searches. search.completed fires when a search you started with POST /v1/searches finishes.

Each event is delivered to your webhook_url and also kept in the feed at GET /v1/events.

Each event type has one meaning and one recommended action. The What to do column is the part to get right.

EventWhat happenedWhat to dodataCost
job.openedA new canonical job appeared for a watched company or saved search.Trigger outbound. This is the only event that should start a sequence.job (full Job)1 credit
job.repostedThe same job was re-listed. repost_count went up.Do not re-trigger outbound. Update your copy of the job only.job (id, title, company, status, repost_count)Free
job.closedThe job is no longer live. closed_reason says why: filled, expired, removed or unknown.Stop sequences tied to this job. Mark it closed in your CRM.job (id, title, company, status, closed_reason)Free
job.updatedA field on an open job changed, for example salary or location.Upsert the job by id. No outbound action.job (full Job)Free
company.hiring_startedis_hiring.value for a watched company changed to true.Good moment for account-level outreach.company, is_hiringFree
company.hiring_stoppedis_hiring.value for a watched company changed to false. A change to null (unknown) never fires this event.Pause hiring-based plays for this account.company, is_hiringFree
search.completedAn async search finished with status completed, partial or failed.Read results with GET /v1/searches/{id}. Branch on status.search (id, status, jobs_found)Free

Every event has the same envelope. data carries the objects relevant to type.

Field Type Meaning
id string Event id (evt_...). Use it to dedupe and to replay.
type EventType One of the types in the catalog above.
created_at date-time When the event was created.
watch_id string | null Watch that produced the event. null for search.completed.
data.job object Full Job for job.opened and job.updated. Id, title, company, status and lifecycle fields otherwise.
data.company, data.is_hiring object For company.hiring_started and company.hiring_stopped.
data.search object id, status, jobs_found for search.completed.

A field missing from data.job is not part of that payload. It is not null and not unknown. Fetch the full job with GET /v1/jobs/{id} if you need it; re-reading a job you already paid for is free.

Illustrative events from the webhook reference. Companies and domains are fictional.

{
"id": "evt_1Kp6MnB3vC",
"type": "job.closed",
"created_at": "2026-10-11T10:00:00Z",
"watch_id": "wat_6Np3QyR8tU",
"data": {
"job": {
"id": "job_01JC8X4M2Q7RV3T9KD5W6YH0AB",
"title": "Head of Revenue Operations",
"company": { "id": "cmp_4Rk7TzP1aQ", "name": "Acme Robotics", "domain": "acme-robotics.example" },
"status": "closed",
"closed_reason": "filled"
}
}
}

A job.opened event carries the full job. Its shape is the record on Jobs.

Route on type, dedupe on id, upsert jobs on data.job.id.

Terminal window
# Read the event feed (free). Pass next_cursor back as since to resume.
curl -s "https://api.betterjobs.cc/v1/events?limit=100" \
-H "Authorization: Bearer $BETTERJOBS_API_KEY" \
-H "BetterJobs-Version: 2026-10-01"
  • BetterJobs POSTs one event per request to your webhook_url, signed with the BetterJobs-Signature header. Verify it before you trust the body.
  • Return any 2xx within 10 seconds.
  • Failed deliveries retry 1m, 5m, 30m, 2h, 6h, 12h, 24h after the first failed attempt, over 24 hours.
  • Replay any event with POST /v1/webhooks/replay. Replays and retries never charge again.
  • Missed something? Read GET /v1/events from your last cursor. It is free.

Signature verification, retries and local testing are covered in Webhooks.

If your plan does not include watches, POST /v1/watches returns 403 plan_required with required_plan. See Errors.

Some providers publish their own event types. Their names map onto BetterJobs events like this:

Provider event (per provider docs) BetterJobs event
Reqbeat opened job.opened
Reqbeat reposted job.reposted
Reqbeat closed job.closed
Reqbeat reobserved No event. last_seen_at moves forward.
TheirStack job.new job.opened
TheirStack job.closed job.closed
TheirStack company.new No direct equivalent. Use a company watch and company.hiring_started.
Operation Endpoint Cost
Create a watch POST /v1/watches Cost: FreeEach job.opened the watch delivers costs 1 credit.
List watches GET /v1/watches Cost: Free
Delete a watch DELETE /v1/watches/{id} Cost: Free
List events GET /v1/events Cost: Free
Replay a webhook POST /v1/webhooks/replay Cost: Free
Event delivery Your webhook_url Cost: 1 credit / job.opened

Recipe: Detect hiring changes.