Webhooks and events
Send signed webhooks to your own systems when jobs, applications, candidates, offers and postings change, refresh your website's job pages, and see which events Careers Orbit sends.
Reference5 min read
Careers Orbit tells other systems what happened in two ways:
- Webhooks under Webhooks send the events you choose to your own systems, such as an HR system. They are in the Growth and Enterprise plans.
- The website webhook under Settings › Website webhook tells your website when a job is published, changed or closed, so it can refresh its job pages. It is in every plan.
Webhooks to your own systems
Admins add them under Webhooks, for example to tell an HR system when someone is hired at Northwilds.
- Choose New webhook, give it a name and the address, such as
https://hr.northwilds.com/hooks/careers. It must start withhttps://. - Tick the events to send. Each is listed under Careers events below.
- Leave Trusted endpoint off unless the system is your own and your privacy notice covers it.
- Copy the signing secret. It starts with
whsec_and is shown once; New secret on the webhook's page replaces it.
Each delivery is a POST with JSON such as:
{ "id": "4f0c…", "event": "careers.application.hired", "time": "2026-10-27T09:12:00.000Z", "organisationId": "…", "subject": { "resourceType": "application", "id": "…" }, "actor": { "type": "user", "id": "…" }, "data": { "applicationId": "…", "jobId": "…", "candidateId": "…", "stageId": "hired", "stageType": "hired", "source": "linkedin" }, "attempt": 1}and these headers:
x-orbit-event: the event typex-orbit-delivery: the delivery's id, the same on every try, so you can ignore repeatsx-orbit-timestamp: the time it was sent, in millisecondsx-orbit-signature: the HMAC-SHA256 of{timestamp}.{body}with your secret, in hexUser-Agent: Careers-Orbit-Webhooks/1
Check the signature against the raw body and refuse messages older than five minutes. In Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';export function verify(secret: string, timestamp: string, body: string, signature: string): boolean { if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60_000) return false; const expected = createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex'); return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}Ids, or contact details too
Payloads carry ids only, so a system can look up what it needs through the management API. A trusted webhook also gets data.candidate with the candidate's firstName, lastName, email and phone on events that name a candidate, and only a trusted webhook may receive careers.notification, the hiring team's notifications, which name candidates. Erasing a candidate removes their details from the delivery log too.
Retries and the log
Answer with a 2xx status within ten seconds. Redirects are not followed and count as a failure. After a failure, Careers Orbit tries again after 1, 2, 4, 8 minutes and so on, at most 6 hours apart and 8 tries in all. After 20 failed deliveries in a row the webhook turns itself off; Turn on starts it again.
Open a webhook to see its latest deliveries from the past 30 days, with the status code, time taken, error and exact JSON. Send again repeats any of them, and Send a test sends careers.webhook.test once to check the address and your signature check. Moving a webhook to the bin stops it.
The website webhook
Set it under Settings › Website webhook, pointing at your website, for example https://northwilds.com/api/careers/revalidate/. Careers Orbit shows a signing secret once; keep it in your website's environment.
When a job is published, changed or closed, Careers Orbit posts JSON such as:
{ "event": "careers.job.closed", "data": { "jobId": "...", "slug": "mountain-guide-lofoten", "title": "Mountain guide, Lofoten", "status": "closed", "departmentId": "...", "locationIds": ["..."] } }with x-orbit-timestamp and x-orbit-signature, signed in the same way. Answer within five seconds. A failed delivery is tried again with growing waits, up to ten times.
The Careers SDK's createRevalidateHandler checks the signature and the age and refreshes the careers:jobs and careers:job:<slug> cache tags. The PHP SDK has Webhook::verify and Webhook::tags for the same job.
Careers events
careers.job.publisheddatajobId, slug, title, status, departmentId, locationIdscareers.job.updateddatacareers.job.closeddatacareers.review.requesteddatajobId, title, requestedBy, notecareers.application.createddataapplicationId, jobId, candidateId, stageId, stageType, sourcecareers.application.stageChangeddatafromStageIdcareers.application.rejecteddatareasoncareers.application.withdrawndatacreatedcareers.application.hireddatacreatedcareers.candidate.eraseddatacandidateId, applications, files (how many went)careers.posting.statusChangeddatajobId, channel, status (incomplete, pending, live, rejected, failed or unpublished), reasoncareers.campaign.sentdatacampaignId, kind (campaign or alert), jobId, recipients, sentcareers.referral.createddatareferralId, jobId, referrerId, invitedcareers.assessment.completeddataassessmentId, applicationId, jobId, candidateId, kind (assessment or video), provider. Never the scorecareers.offer.sent, .accepted, .declineddataofferId, applicationId, jobId, candidateId, statuscareers.notificationdataid, recipient, kind, title, detail, url, createdAt| Event | When | data |
|---|---|---|
careers.job.published | A job goes live, at once or at its scheduled time | jobId, slug, title, status, departmentId, locationIds |
careers.job.updated | A live job changes | as above |
careers.job.closed | A job closes, by hand, on its closing date or by going to the bin | as above |
careers.review.requested | A job ad is sent for review | jobId, title, requestedBy, note |
careers.application.created | A new application arrives | applicationId, jobId, candidateId, stageId, stageType, source |
careers.application.stageChanged | An application moves to another stage | as above, and fromStageId |
careers.application.rejected | An application is rejected | as above, and reason |
careers.application.withdrawn | A candidate withdraws | as for created |
careers.application.hired | An application ends in a hire | as for created |
careers.candidate.erased | A candidate's data is erased, by hand, on request or by your retention rules. Erase your copies too | candidateId, applications, files (how many went) |
careers.posting.statusChanged | A job's posting on a job board changes | jobId, channel, status (incomplete, pending, live, rejected, failed or unpublished), reason |
careers.campaign.sent | A talent pool campaign or a new job alert has finished going out | campaignId, kind (campaign or alert), jobId, recipients, sent |
careers.referral.created | Someone in your team refers a person, or a person applies through a referral link | referralId, jobId, referrerId, invited |
careers.assessment.completed | A candidate completes an assessment or video interview | assessmentId, applicationId, jobId, candidateId, kind (assessment or video), provider. Never the score |
careers.offer.sent, .accepted, .declined | An offer goes to a candidate, and they accept or decline it | offerId, applicationId, jobId, candidateId, status |
careers.notification | A notification to the hiring team. Trusted webhooks only | id, recipient, kind, title, detail, url, createdAt |
Every event is version 1 and carries your organisation, a sequence number and who caused it (actor.type is user or system). Careers Orbit also sends these events to Orbit Exchange, where other Orbit satellites and Orbit's Exchange page see them. To receive them in your own systems, use a webhook.
Was this page helpful?