Webhook Consumer Runbook: Stop Replay Attacks in Hiring
A platform runbook for preventing forged or replayed hiring events from corrupting your ATS funnel, risk flags, and audit trail.

If a webhook can advance a candidate, it is a privileged action. Privileged actions need verification, replay defense, and a traceable audit trail.Back to all posts
Owners
Security: signature standards, key rotation, replay window policy, audit logging requirements.
Recruiting Ops: which ATS state transitions are allowed from webhook events, and which require manual review.
Analytics or Chief of Staff: anomaly detection on event volumes, latency, and state-change rates.
Automated vs manual
Automated: accept webhook -> verify signature -> dedupe -> write immutable event log -> update candidate status.
Manual review: any event that fails verification, arrives outside replay window, or attempts a high-impact state change.
Sources of truth
ATS: hiring workflow state (stage, disposition, offer).
Verification service: identity decision and evidence references.
Interview and assessment services: score artifacts and proctoring or fraud signals.
Forged event: attacker posts a "passed" or "cleared" event to move a candidate through gates.
Replay attack: a previously valid event is resent to trigger duplicate transitions, clear a flag, or overwrite a newer decision.
Out-of-order delivery: legitimate events arrive late, and your consumer mistakenly applies stale truth.
Store the raw payload as received.
Decide an allowlist of algorithms (for example, HMAC-SHA256).
Fail closed for missing headers.
Reject if |now - event_timestamp| > tolerance.
Choose tolerance based on your worst-case delivery delay (include retries). Many teams start with 5 minutes as an illustrative example, then adjust using real latency telemetry.
Idempotency key: provider + event_id.
Storage: Redis with TTL, or a database unique constraint.
Behavior: if duplicate, return 200 OK but do nothing. This reduces retries and reviewer fatigue.
Store decision_version or decision_timestamp.
Only update candidate identity state if the incoming event is newer.
Generate a trace_id per event.
Log: trace_id, event_id, candidate_id, job_req_id, signature_status, replay_status.
Dashboard: signature failure rate, replay rejects, time skew distribution.
When ATS is down: keep accepting and verifying webhooks, write to an immutable event store, and pause ATS updates.
When ATS recovers: replay from the event store in order, respecting idempotency.
Kill switch: toggle "no state mutations" while still collecting evidence for later reconciliation.
Phase 1: verify signatures, but do not reject. Record pass/fail and reason.
Phase 2: hard-fail in canary environments or a subset of events.
Phase 3: enforce globally, then rotate secrets on a schedule.
name: integritylens
eventType: "identity.verified"
from: ["applied", "screening"]
eventType: "risk.flagged"
from: ["any"]
trace_id
provider
event_id
event_type
candidate_id
job_req_id
signature_status
replay_status
decision_timestamp
name: webhook_signature_failure_rate
name: webhook_replay_reject_count
Accept first, verify later: parsing and state updates happen before you know the event is authentic.
Using only IP allowlists: they fail under NAT, proxies, and provider infra changes, and they do not stop replays.
Letting webhooks directly edit ATS stages without monotonic rules: stale or duplicated events overwrite the real candidate state.
TA leaders use it to keep the funnel moving while gating risky candidates.
Recruiting Ops uses it to standardize stage transitions and exception queues.
CISOs use Risk-Tiered Verification, Evidence Packs, and Zero-Retention Biometrics to reduce audit findings.
Platform teams integrate via Idempotent Webhooks and traceable event models so your consumers can verify signatures, block replays, and reconcile cleanly after outages.
Can we prove every ATS state change was caused by a verified event tied to a candidate_id?
What is our current signature failure rate, and is it drifting after provider changes?
If the ATS is unavailable for 2 hours, do we lose events, or do we replay safely?
Who can flip the kill switch, and what is the on-call procedure?
https://checkr.com/resources/articles/hiring-hoax-manager-survey-2025 structuredSections:[

Related Resources
Key takeaways
- Treat hiring webhooks as security events, not just integration plumbing, because they can change identity and risk state.
- Signature verification without replay defense is incomplete. You need timestamp windows plus nonce or event-id dedupe.
- Idempotency and observability are the difference between a clean funnel and a week of reconciliation after an incident.
- Make the ATS the hiring workflow source of truth, but never let the ATS accept identity state changes unless the webhook is verified and traceable.
A change-control friendly policy for your IntegrityLens webhook consumer.
Defines signature verification, replay defense, idempotency behavior, allowed ATS transitions, observability fields, and emergency kill switches.
webhookConsumerPolicy:
service: integrity-events-consumer
purpose: "Accept IntegrityLens hiring events and update ATS state only when events are authentic, fresh, and idempotent."
acceptedProviders:
- name: integritylens
signature:
scheme: hmac-sha256
headerSignature: "X-IL-Signature"
headerTimestamp: "X-IL-Timestamp"
signedPayloadFormat: "{timestamp}.{raw_body}" # verify exact raw bytes
clockSkewToleranceSeconds: 300
keyManagement:
secretRef: "gcp-secret-manager://prod/il_webhook_signing_secret"
rotation:
cadenceDays: 90
overlapDays: 7
replayDefense:
idempotencyKey: "integritylens:{event_id}"
store:
type: redis
ttlSeconds: 604800 # 7 days to cover retries and reconciliation
onDuplicate: "ack_and_noop"
stateMutationRules:
- eventType: "identity.verified"
allowedAtsTransitions:
- from: ["applied", "screening"]
to: "verified"
requireEvidenceRef: true
monotonicBy: "decision_timestamp"
- eventType: "risk.flagged"
allowedAtsTransitions:
- from: ["any"]
to: "manual-review"
requireEvidenceRef: true
quarantine:
onSignatureFail: "queue:il_webhooks_quarantine"
onTimestampOutsideWindow: "queue:il_webhooks_quarantine"
onMissingFields: "queue:il_webhooks_quarantine"
observability:
requiredLogFields:
- trace_id
- provider
- event_id
- event_type
- candidate_id
- job_req_id
- signature_status
- replay_status
- decision_timestamp
metrics:
- name: webhook_signature_failure_rate
alertIfAbovePercent: 1
windowMinutes: 15
- name: webhook_replay_reject_count
alertIfAbove: 5
windowMinutes: 15
emergencyControls:
killSwitch:
flagName: "DISABLE_ATS_STATE_MUTATIONS"
behaviorWhenEnabled: "verify_and_store_only"
backfill:
sourceOfTruth: "immutable_event_store"
applyMode: "ordered_replay_with_idempotency"Outcome proof: What changes
Before
Webhook consumer accepted events based on shared secrets in application code, had no replay window, and wrote directly to ATS stages. When events arrived out of order or were duplicated, Ops had to manually reconcile candidate state and explain discrepancies in leadership reviews.
After
Webhook consumer enforces signature verification on raw payloads, blocks replays with signed timestamps plus idempotency keys, and applies monotonic state rules. Events are traceable end-to-end via trace_id and candidate_id, and ATS updates can be paused with a kill switch while verified events continue to be captured for replay.
Implementation checklist
- Reject unsigned webhooks and enforce a strict signature algorithm allowlist.
- Enforce a replay window using a signed timestamp and a server-side tolerance (for example, 5 minutes).
- Deduplicate by event_id (or nonce) with an idempotency store keyed by provider + event_id.
- Log a trace_id that ties webhook event -> candidate_id -> req_id across ATS, verification, and assessment systems.
- Add a kill switch to pause webhook-driven state transitions while continuing to capture events to a quarantine queue.
- Run a canary rollout: verify signatures in shadow mode, then flip to hard-fail once false positive rate is acceptable.
Questions we hear from teams
- What is the minimum viable defense for webhook consumers in hiring workflows?
- Verify a cryptographic signature over the raw payload and enforce idempotency using an event_id. Without those two controls, forged or duplicated events can change candidate state and corrupt funnel reporting.
- Why do replay attacks matter if we verify signatures?
- A replayed request can still have a valid signature because it was originally signed. You need a signed timestamp plus a server-side tolerance window, and ideally event_id deduplication, to ensure each event is applied at most once.
- What should happen when signature verification fails?
- Do not update ATS stages. Quarantine the event with enough context to investigate (headers, raw body hash, timestamp skew, trace_id) and route it to a Security-reviewed exception queue with an SLA.
- How do we keep hiring moving if the ATS is down?
- Decouple acceptance from application: keep verifying and storing events in an immutable event store, pause ATS mutations, then replay in order with idempotency once the ATS recovers.
Ready to secure your hiring pipeline?
Let IntegrityLens help you verify identity, stop proxy interviews, and standardize screening from first touch to final offer.
Watch IntegrityLens in action
See how IntegrityLens verifies identity, detects proxy interviewing, and standardizes screening with AI interviews and coding assessments.
