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.

IntegrityLens office visual
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:[

What is IntegrityLens

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.
Webhook Consumer Security PolicyYAML policy

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.

Governance Notes: Legal and Security signed off because the design minimizes data exposure (store raw payloads with access controls, encrypt at rest with 256-bit AES baseline), limits biometric handling via privacy-first principles (Evidence Packs reference artifacts rather than duplicating them broadly), and enforces least-privilege access to logs. The process includes an appeal and correction flow: quarantined events do not mutate ATS state until reviewed, and all overrides are logged with operator identity and reason codes.

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.

Try it free Book a demo

Watch IntegrityLens in action

See how IntegrityLens verifies identity, detects proxy interviewing, and standardizes screening with AI interviews and coding assessments.

Related resources