Flowmingo Logo

Integrations

Webhook Integration Guide

Subscribe HTTPS endpoints to Flowmingo events, validate signatures, and stream candidate updates directly into your systems.

Need help integrating?

Stuck on a step or need technical support? Our team is happy to help — reach out any time and we'll get you connected.

Webhook Integration

Flowmingo emits notifications whenever candidates move through invitation, interview, or evaluation workflows. This section explains how to create webhook endpoints, secure them with HMAC signatures, and understand the payloads you will receive so downstream systems (ATS, HRIS, Slack bots, etc.) stay in sync.

1. Create a webhook endpoint

Log in to your Flowmingo workspace and navigate to Settings -> Webhooks. Click Create Endpoint and provide:

  • Name: A label to identify this endpoint later.
  • URL: A publicly accessible HTTPS endpoint you control (for example: https://your-app.com/api/flowmingo-webhook).
  • Events: Event types this endpoint should receive.
  • Description (optional).

On save, Flowmingo returns a webhook secret. Store it securely. You need this value to verify X-Webhook-Signature.

Managing endpoints:

  • Update/Delete: Return to Settings -> Webhooks, open an endpoint, and edit or delete it.
  • Regenerate Secret: Rotate the signing secret and update your verifier before removing the old secret.
  • Send Test: Trigger a test webhook to validate your endpoint without waiting for a real event.

2. Supported events

Event Description Typical trigger
invitation.status.update Invitation email lifecycle updates (accepted, delivered, opened, clicked, failed). Notification service after Flowmingo sends interview invitations.
interview.status.update Candidate starts/completes an interview. Candidate interview emits state changes.
interview.evaluation.update New CV/interview/holistic evaluation results are stored. Evaluation processor finishes scoring.

Subscribing to a parent event (e.g., invitation.status.update) receives all sub-events of that type. You can also subscribe to specific sub-events only (e.g., invitation.status.update.invitation_email_failed).

3. Payload structure

Flowmingo wraps every event in a consistent envelope:

{
  "schema_version": "1.0.0",
  "event_type": "invitation.status.update",
  "event_id": "3a74fa26-4b66-4534-ba76-073548c95239",
  "timestamp": "2026-03-04T01:00:00.000Z",
  "organization_id": 123,
  "data": {
    "...event specific fields..."
  },
  "is_test": true
}

is_test is only included for test deliveries.

Invitation status payload

Event name: invitation.status.update

{
  "interview_set_id": "019bcb66-2d6e-795a-9770-148355e51c51",
  "candidate_id": "019bcb66-66d9-781e-96cc-e4a2b74c7fce",
  "candidate_email": "alex@example.com",
  "status": "invitation_email_delivered",
  "reason": "delivered"
}

reason is always present and is never null — it mirrors the provider signal (delivered, opened, clicked) or names the failure (invalid_email, unsubscribed, bounce, dropped). interview_name appears only on failures we raise ourselves (invalid_email, unsubscribed); it is absent on the provider-reported delivered / opened / clicked events.

Possible status values:

  • invitation_email_delivered - Email Delivered
  • invitation_email_opened - Email Opened
  • invitation_email_clicked - Link Clicked
  • invitation_email_failed - Email Failed
  • invitation_email_spam - Marked as Spam
  • invitation_email_accepted - reserved; nothing sends it today

Interview status payload

Event name: interview.status.update

{
  "interview_set_id": "019bcb66-2d6e-795a-9770-148355e51c51",
  "interview_name": "Sales Assessment",
  "candidate_id": "019bcb66-66d9-781e-96cc-e4a2b74c7fce",
  "candidate_email": "alex@example.com",
  "status": "started"
}

Possible status values:

  • initiated - Candidate opened the interview link
  • started - Interview Started
  • completed - Interview Completed

Interview evaluation payload

Event name: interview.evaluation.update

{
  "interview_set_id": "string",
  "interview_name": "string",
  "candidate_id": "string",
  "candidate_email": "string",
  "evaluation_type": "cv" | "interview" | "holistic",
  "evaluation_score": "number | null",
  "score_status": "scored" | "cannot_evaluate" | "not_scored",
  "submission_id": "string",
  "submission_url": "string"
}

Possible evaluation_type values:

  • cv - CV Evaluation
  • interview - Interview Evaluation
  • holistic - CV + Interview Evaluation

evaluation_score and score_status. The score is a decimal out of 10 (for example 8.4). It is null whenever there is no usable number, and score_status says why:

  • scored — a real score.
  • cannot_evaluate — the AI ran and could not rate the attempt (too little to go on). This is not a zero, and it is not a failure on your side.
  • not_scored — no score exists yet.

When it arrives. For evaluation_type interview and holistic this event is held back while the candidate's improvement window is open, and re-sent by a job that runs every ten minutes once the window closes — so it can land some minutes after the interview finished. A cv evaluation is not held.

submission_id is the id to pass to GET /company/integration/hiring/submissions/{id}/detail/v1 for the full evaluation: the written summary, strengths, gaps, recommendations and per-criterion scores. See Read results through the API.

Note: payloads gain fields over time. Read the keys your integration needs and ignore the rest; a new key is not a breaking change. On real deliveries data carries no timestamp of its own — use the envelope's. (A Send test payload does include a data.timestamp; do not build a parser that needs it.)

Retries

If your endpoint returns non-2xx or fails, Flowmingo retries with exponential backoff: up to 6 attempts in total, starting ~2 minutes after the first failure and doubling each time, capped at 2 hours between attempts. After the 6th the event is left in an error state and is not retried again.

An endpoint that keeps failing is disabled automatically. After 15 consecutive failed deliveries we deactivate it and stop sending, so a dead URL does not accumulate forever. Any single 2xx resets the counter. If deliveries stop arriving, check the endpoint's status under Settings -> Webhooks before assuming no events occurred.

4. Signature verification

Every delivery includes an X-Webhook-Signature header:

X-Webhook-Signature: t=1732619345,v1=3f2fdd0af4...
  • t is the UNIX timestamp (seconds).
  • v1 is an HMAC-SHA256 digest of timestamp + '.' + body.
  • The signing key is the hex part only — strip the whsec_ prefix. Flowmingo displays the secret as whsec_<hex>, but signatures are computed with <hex> alone. Signing with the full whsec_... string produces a different digest and every verification fails.

Example middleware (Node.js/Express) that validates the signature using the raw request body:

import crypto from 'crypto';

const WEBHOOK_SECRET = process.env.FLOWMINGO_WEBHOOK_SECRET!;

function verifySignature(rawBody: Buffer, header: string | string[] | undefined) {
  if (!header || Array.isArray(header)) return false;
  const parts = header.split(',');
  const timestamp = parts.find((p) => p.startsWith('t='))?.split('=')[1];
  const signature = parts.find((p) => p.startsWith('v1='))?.split('=')[1];
  if (!timestamp || !signature) return false;

  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody.toString('utf8')}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'));
}

Reject requests if signature verification fails or if the timestamp is outside your allowed skew window.

Webhook Issues

  • Signature verification failures: Ensure you're using the raw request body (not parsed JSON) when computing the HMAC signature. The timestamp must match exactly what's in the header.

  • Missing events: Check that your endpoint URL is correct, HTTPS, and publicly accessible. Verify the endpoint is active and subscribed to the correct event types.

  • Retry behavior: see Retries in section 3 — up to 6 attempts with exponential backoff, then the endpoint auto-disables after 15 consecutive failures. Answer with any 2xx to acknowledge receipt.

  • Respond within 10 seconds. Deliveries time out at 10s, and a timeout counts as a failure. Acknowledge first and do your processing afterwards; an endpoint that works synchronously and answers in 12s will fail every delivery and eventually be disabled.

  • Redirects are treated as failures, not followed. Point the endpoint at its final URL — an address that 301s to a canonical host (adding www., or http to https) never receives a delivery.