LessRounds
  • Pricing
Log inStart free
LessRounds

An AI interviewer that holds the first interview with each job applicant. You share it as a link. Fewer rounds. Better hires.

hello@lessrounds.ai

Product

  • How it works
  • AI interviewer
  • Scoring
  • Interview builder
  • Interview links
  • Review
  • Candidate experience
  • Languages
  • Pricing

Solutions

  • All solutions
  • High-volume hiring
  • Campus hiring
  • Hourly hiring
  • Staffing agencies
  • Replace phone screens
  • All industries
  • Customer support
  • Retail

Resources

  • Guides
  • Screening questions
  • Email templates
  • Free calculators
  • Glossary
  • For candidates
  • API and webhooks
  • FAQ
  • Responsible AI
  • Security

Compare

  • All comparisons
  • Best AI interview software
  • HireVue alternatives
  • Spark Hire alternatives
  • Willo alternatives
  • LessRounds vs Ribbon

Company

  • About
  • Manifesto
  • Contact

Legal

  • Privacy
  • Terms
  • Candidate privacy
  • Data processing (DPA)
  • Subprocessors
  • Cookies
  • Acceptable use

© 2026 LessRounds is a product of OLN Labs, operated by OLOG N Solutions Technology LLP.

Bengaluru, India

  1. Home
  2. Developers
  3. Webhooks

Webhooks

Get a message when something happens to a candidate.

LessRounds can send a message to a web address of yours when a candidate finishes, is scored, has a final result or changes status. Here’s how to set it up and what each message contains.

Updated October 3, 2026

On this page

  • Add a webhook
  • The four events
  • What a message looks like
  • Repeats, order and the latest data
  • When your address doesn’t answer
  • Check that a message comes from LessRounds (optional)
  • CVs, recordings and transcripts
  • Which addresses are allowed

A webhook is a web address that receives a message from LessRounds when something happens to a candidate: they finish the interview, the AI scores it, their result is final, or their status changes. Tools like Make and n8n give you such an address, and so can your own server.

Add a webhook

  1. In LessRounds, go to Settings → API & webhooks and click Add webhook. Only admins can.
  2. Paste the address. It must start with https://.
  3. Choose which events to send, and for which interviews: all of them (including ones you create later), or only some.
  4. Click Send a sample, choose an interview, and send. The sample is a made-up candidate with that interview’s real questions, marked "test": true, so your tool can learn every field.

A company can have up to 10 webhooks. Each one has a delivery log showing the last 30 days of messages and what your address answered.

The four events

EventIn LessRoundsWhen it’s sent
candidate.submittedCandidate finishedSent once per attempt, when the candidate has finished every step.
candidate.scoredAI score readySent once per attempt, when the AI score is saved (never before candidate.submitted). Not sent when the interview couldn’t be scored.
candidate.readyResult is finalSent once per attempt, when the result is final: complete (score and recording), incomplete (result.incomplete_reason says why) or not_applicable (no AI step). Nothing more arrives for this attempt except status changes.
candidate.status_changedStatus changedSent on every status change after submission, by the AI or your team. change says from what, to what, and who made it.

Which should I choose? For one message per candidate with everything in it, choose only Result is final (candidate.ready). Add Status changed if you also want your team’s decisions, like shortlisting or rejecting.

Nothing is sent before a candidate finishes, and nothing is sent for previews your team takes.

What a message looks like

Each message is a POST request with a JSON body. Here’s a status change, with the candidate shortened:

{
  "id": "Hk3d9xQ2mZpL0aB7cD1eF",
  "type": "candidate.status_changed",
  "created_at": "2026-10-03T10:41:00Z",
  "api_version": "v1",
  "test": false,
  "change": {
    "from": "passed",
    "to": "shortlisted",
    "by": { "type": "team", "name": "Priya Nair" }
  },
  "candidate": {
    "id": "V1StGXR8_Z5jdHi6Bmy2k",
    "attempt": 1,
    "name": "Riya Sharma",
    "email": "riya@example.com",
    "interview": { "id": "k9TzQ1bL7xR3mN5pW8vY2", "title": "Customer Support Associate" },
    "status": "shortlisted",
    "status_label": "Shortlisted",
    "result": { "state": "complete", "score": 82, "recommendation": "recommended" },
    "answers": {
      "q-x7k2m9a1b3": { "question": "What is your notice period?", "answer": "30 days" }
    },
    "dashboard_url": "https://lessrounds.ai/dash/pipeline/V1StGXR8_Z5jdHi6Bmy2k"
  }
}

candidate is the same object the API returns. Every field is described in the API reference. The transcript isn’t included. Get it from the API when you need it.

Headers

HeaderWhat it is
LessRounds-Event-IdThe event id (the same as the body’s id). Stays the same on every retry: use it to ignore a message you already handled.
LessRounds-Event-TypeThe event type, the same as the body’s type.
LessRounds-Delivery-Attempt1 for the first try, 2 for the first retry, and so on (at most 9).
LessRounds-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook’s signing secret>. For 24 hours after you replace the secret there are two v1 values, one per secret. Checking it is optional.

Fields

FieldTypeWhat it is
idstringStays the same if you receive the event again — use it to ignore repeats.
typestringOne of: candidate.submitted, candidate.scored, candidate.ready, candidate.status_changed
created_attimeWhen it happened.
api_versionstringAlways "v1"
testbooleantrue for samples sent from LessRounds; real events are false.
changeStatusChange or nullOnly on candidate.status_changed.
candidateCandidateThe candidate's latest data — not frozen at the time of the event.

Repeats, order and the latest data

  • The same message can arrive twice, for example when your address answered too slowly the first time. Its id stays the same, so you can ignore one you’ve already handled.
  • candidate is always the latest data, not a copy from the moment of the event. A late or repeated message never brings back older data. What changed at the time is kept in change.
  • Messages about one candidate arrive in the order they happened. A message waiting to be retried holds back that candidate’s later messages to the same address. Other candidates aren’t held up.

When your address doesn’t answer

Your address must answer with any 2xx status within 10 seconds. Redirects aren’t followed and count as a failure. If it fails, we try again after 1, 5 and 30 minutes, then after 2, 6, 12, 24 and 24 hours: 9 tries over about 3 days.

  • After a day of failures, your admins get an email.
  • After 3 days with no success, the webhook is turned off, messages still waiting are marked failed, and your admins get a second email.
  • When it works again, turn the webhook back on (it gets new events from then on) and resend what you missed from its delivery log. Or catch up with GET /events, which keeps 30 days.
  • If you change the webhook’s address, its waiting messages are sent to the new address straight away.

Check that a message comes from LessRounds (optional)

Every message is signed with your webhook’s signing secret. Find it in the webhook’s menu, under Signing secret. Checking the signature is optional: the addresses Make and n8n create are long and random, so most people skip it. Your own server should check it.

The LessRounds-Signature header looks like t=1791019260,v1=5f2c…. To check it, compute an HMAC-SHA256 of the timestamp, a dot and the raw body, using the whole secret (including whsec_) as the key. Compare it with v1, and reject the message if t is more than 5 minutes away from now.

Node.js

import crypto from 'node:crypto';

// body: the raw request body as a string, before JSON.parse. header: LessRounds-Signature.
export function isFromLessRounds(body, header, secret) {
  const parts = header.split(',').map((p) => p.trim().split('='));
  const t = parts.find(([k]) => k === 't')?.[1];
  const signatures = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest('hex')
  );
  return signatures.some(
    (s) => s.length === expected.length && crypto.timingSafeEqual(Buffer.from(s), expected)
  );
}

Python

import hashlib, hmac, time

# body: the raw request body as bytes, before parsing. header: LessRounds-Signature.
def is_from_lessrounds(body: bytes, header: str, secret: str) -> bool:
    parts = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
    t = next((v for k, v in parts if k == "t"), None)
    signatures = [v for k, v in parts if k == "v1"]
    if t is None or abs(time.time() - int(t)) > 300:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(s, expected) for s in signatures)

Use the body exactly as it arrived. Parsing and re-encoding the JSON changes it, and the check fails.

Replacing the secret: in the webhook’s menu, under Signing secret, click Replace secret. For the next 24 hours, messages carry two v1 values, one for each secret, so you can switch without missing any.

CVs, recordings and transcripts

Links in a message (documents[].url, ai_interview.recording.url, ai_interview.transcript_url) point to the API, so they need your API key. A CV or recording never sits at a public address. Calling a file or recording link with your key gives a download_url that works for 15 minutes.

To let people open a candidate from your sheet or tracking system, use dashboard_url. It opens the candidate’s page in LessRounds for anyone on your team who is signed in.

Which addresses are allowed

Addresses must use https:// and a public host name. For safety, we never send messages to private, local or internal network addresses, and we check this again each time we connect.

Questions

Can I get a webhook for a candidate who started but didn’t finish?

No. Messages start when a candidate finishes. To see who is still taking an interview, ask the API: GET /candidates?state=in_progress.

Why didn’t I get “AI score ready” for a candidate?

It isn’t sent when the AI couldn’t score the interview, for example when the candidate said too little. “Result is final” still arrives, with result.state set to incomplete and the reason in result.incomplete_reason.

Who can see webhooks?

Admins add and change them. Other team members can see whether each webhook is working, but not its full address or its signing secret.

Do webhooks cost anything?

No. Webhooks and the API are free and use no credits.

Keep reading

  • API referenceEvery endpoint of the LessRounds API: interviews, candidates, transcripts, recordings, files and the change history, with each field explained in plain words.
  • Send your interview results to other toolsSend each candidate’s score, answers and status to a Google Sheet, your applicant tracking system or your own software, with webhooks and a read-only API.
  • How we protect your dataWhere LessRounds stores interview data, who can see it, how sign-in works, how data is protected, and which outside companies help us process it.

Try LessRounds free today.

Start with 100 free credits, enough for about 20 interviews. No credit card or sales call needed.

Start freeBook a demo