Engineering

How to Use Webhooks to Sync Candidates With Slack and HubSpot

ClarityHire Team(Editorial)4 min read

What an ATS webhook actually does

A webhook is a hook the ATS pulls when something happens — a candidate applies, a test is completed, an interview is scheduled. The ATS sends a small JSON payload to a URL you provide. Your code reads it and does something downstream: post to Slack, create a HubSpot contact, update a Greenhouse stage, kick a Zap.

Webhooks are cheap, push-based, and event-shaped. You don't poll. You don't reimplement the data model. You react.

The events worth wiring up first

Eleven events fire on ClarityHire's webhook surface, but only a handful matter on day one:

  • application.submitted — new candidate in the pipeline. The Slack-notification event.
  • test.completed — assessment is graded. The "ping the recruiter" event.
  • test.passed / test.failed — auto-advance / auto-archive triggers.
  • interview.scheduled and interview.completed — calendar and CRM sync.
  • candidate.stage_changed — the "tell HubSpot the lead heated up" event.
  • candidate.hired — close the loop with finance / IT provisioning.

Wire application.submitted and candidate.stage_changed first. Everything else can wait until you've learned what your team actually wants notifications about.

Pattern 1 — Slack candidate alerts

The naive version: one webhook → one Incoming Webhook URL in Slack → a flat message in #hiring. Fine for a week. Painful after that, because every new applicant pings everyone.

The pattern that holds up:

  1. Route by job. Map each job to a Slack channel (#hiring-backend, #hiring-design). Receive the webhook in a tiny serverless function, look up the job → channel mapping, post there. Five-line function.
  2. Threaded updates per candidate. Post the initial application.submitted event as a top-level message. Post subsequent events (test.completed, interview.scheduled) as replies in the same thread. Now each channel reads like a per-candidate timeline.
  3. One actionable button. Add a Slack action button that links to the candidate profile in the ATS. Don't try to do the actual decision in Slack; do it where the data is.

The reason to put a tiny function in the middle (instead of using Slack's native incoming webhook directly) is so you can verify the HMAC signature, look up state, and not leak a Slack URL to the entire org's ATS config.

Pattern 2 — HubSpot contact sync

HubSpot's value in a hiring pipeline is the same as in a sales pipeline: keep a record of every person who has ever interacted with the org, tagged by lifecycle stage. Hiring leads are people you might re-engage in three years.

  1. application.submitted → create/update HubSpot contact. Set lifecycle stage to Other or a custom Candidate stage. Attach the source job as a property.
  2. candidate.hired → update lifecycle to Customer/Employee. Suppresses recurring marketing emails to people who now work for you.
  3. application.rejected → tag for talent rediscovery. A "rejected but qualified" tag is the single highest-ROI tag in a HubSpot/ATS bridge. Six months later when you have a similar req, your sourcer queries the tag.

Use HubSpot's batch API for the writes if you're at any scale. Single-contact writes are fine to start, painful at 50 reqs/min.

The gotchas that bite in production

  • Verify the HMAC signature on every request. The webhook URL is internet-accessible. If you don't verify, anyone who finds your URL can spam your Slack channel. ClarityHire's webhooks ship with a signing secret per endpoint — use it.
  • Make handlers idempotent. Webhooks retry on non-2xx responses. Your handler will see the same event more than once. Key your downstream actions on the event ID, not on receipt time.
  • Reply 2xx fast, then do the work. A webhook handler that takes 30 seconds to call HubSpot before returning 200 will time out and get retried. Acknowledge first, queue the work, do it async.
  • Log delivery, separately from acting. The ATS will give you a delivery log; build your own too. When something goes missing, the question "did the ATS send it, did we receive it, did we act on it" needs three separate answers.

How ClarityHire's webhook layer is wired

/dashboard/webhooks lets you register an endpoint per organisation, choose which events to subscribe to, set custom headers, and view the per-delivery log with HMAC signature, payload, response code, and replay button. Failed deliveries retry with exponential backoff. For lighter integrations you can also route through Zapier or use the native Slack + HubSpot connectors and skip the custom endpoint entirely.

TL;DR

Start with two events (application.submitted, candidate.stage_changed), one Slack channel per job, and one HubSpot contact-sync flow. Verify HMAC, make handlers idempotent, ack fast and queue. Everything else scales from that base.

ats webhooksslack candidate notificationshubspot ats synchiring automationwebhook integration

Related Articles