Für Entwickler

API-Referenz

Der komplette KI-Interview-Workflow, verfügbar über einfaches REST und JSON. Erstellen Sie Interviews, laden Sie Kandidaten in großem Umfang ein, lesen Sie bewertete Ergebnisse und erhalten Sie Webhooks, sobald ein Bericht bereit ist.

Basis-URLhttps://api.airbasehq.com/v1

Erste Schritte

Die API ist eine versionierte REST-Schnittstelle unter /v1. Anfragen und Antworten sind JSON. Ressourcen werden über kurze numerische Codes adressiert, niemals über interne IDs, sodass alles, was Sie in einer Antwort sehen, sicher in Ihren eigenen Systemen gespeichert werden kann.

Um zu starten, brauchen Sie nur eines: einen API-Schlüssel. Erstellen Sie ihn in der App unter Workspace, dann API Keys. Der vollständige Schlüssel wird bei der Erstellung nur einmal angezeigt, kopieren Sie ihn also sofort und bewahren Sie ihn wie ein Passwort auf.

Ihre erste Anfrage
curl https://api.airbasehq.com/v1/me \
  -H "Authorization: Bearer $API_KEY"
Antwort 200
{
  "company": { "name": "Acme Inc." },
  "apiKey": {
    "name": "Production automation",
    "permissions": {
      "canManageInterviews": true,
      "canManageApplications": true
    }
  }
}

Authentifizierung

Jede Anfrage trägt Ihren API-Schlüssel als Bearer-Token. Schlüssel sind auf Ihren Arbeitsbereich beschränkt, sodass niemals eine Firmen-ID in einem Pfad erscheint.

Schlüssel sehen aus wie cuee_<keyId>_<secret>. Auf unserer Seite wird nur ein Hash des Geheimnisses gespeichert, weshalb der Schlüssel bei der Erstellung nur ein einziges Mal angezeigt wird. Schlüssel können ein Ablaufdatum erhalten und jederzeit auf derselben Seite widerrufen werden.

Auth-Header
Authorization: Bearer cuee_0123456789abcdef0123456789abcdef_<64 hex chars>
Content-Type: application/json
  • Ein fehlender oder fehlerhafter Schlüssel gibt 401 authentication_required zurück. Ein widerrufener oder abgelaufener Schlüssel gibt 401 mit invalid_api_key oder api_key_expired zurück.
  • Ein gültiger Schlüssel, dem eine erforderliche Berechtigung fehlt, gibt 403 insufficient_permissions zurück.

Berechtigungen

Wenn Sie einen Schlüssel erstellen, wählen Sie, was er tun darf. Gewähren Sie nur, was Ihre Integration benötigt. Diese drei Scopes decken heute die gesamte öffentliche Schnittstelle ab:

ScopeBezeichnung in der AppWas er freischaltet
canManageInterviewsInterviewsKI-Interviews erstellen und verwalten, Kandidaten einladen, Interviewergebnisse prüfen.
canManageApplicationsTalent PoolKandidaten, Bewerber und zugehörige Bewerbungsdatensätze anzeigen und verwalten.
canViewApplicationsView ApplicationsBewerbungs- und Kandidatendatensätze lesen. Außerdem erforderlich für die Verwaltung von Webhooks.
  • In KI-Interviewer-Arbeitsbereichen erstellte Schlüssel tragen dieses Bündel automatisch, ohne dass eine Auswahl nötig ist.
  • Die Endpunkte führen ihre erforderlichen Scopes unten auf. Wo zwei Scopes aufgeführt sind, benötigt der Schlüssel beide.

Ratenbegrenzungen

Jeder API-Schlüssel darf 200 Anfragen pro 60 Sekunden über die gesamte /v1-Schnittstelle stellen. Jede Antwort enthält die Header X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset. Wird das Limit überschritten, folgt HTTP 429 mit einem Retry-After-Header, sodass ein gut funktionierender Client einfach warten und es erneut versuchen kann.

Es gibt Batch-Endpunkte, sodass Sie selten viele Anfragen benötigen: ein einzelner Einladungsaufruf akzeptiert bis zu 500 Kandidaten.

Fehler

Fehler verwenden überall dieselbe stabile Hülle. Das Feld code ist eine maschinenlesbare Konstante, die niemals umbenannt wird, verzweigen Sie also danach statt nach der Nachricht.

Fehler-Hülle
{
  "error": {
    "code": "ai_interview_not_published",
    "message": "Publish the interview before inviting candidates."
  }
}
HTTPCodesBedeutung
400invalid_request, validation_failedDer Anfragetext oder die Abfrage hat die Validierung nicht bestanden.
401authentication_required, invalid_api_key, api_key_expiredDer Schlüssel fehlt, ist fehlerhaft, widerrufen oder abgelaufen.
403insufficient_permissions, ai_interview_not_publishedDem Schlüssel fehlt ein Scope, oder das Interview kann noch keine Kandidaten aufnehmen.
404not_found, ai_interview_not_found, candidate_not_foundDie Ressource existiert nicht in Ihrem Arbeitsbereich.
429rate limitedZu viele Anfragen. Beachten Sie den Retry-After-Header.
500operation_failedAuf unserer Seite ist etwas fehlgeschlagen. Ein erneuter Versuch mit Backoff ist sicher.

Arbeitsbereich

Identifizieren Sie den Arbeitsbereich hinter einem Schlüssel und prüfen Sie, was der Schlüssel tun darf. Nützlich als Verbindungstest.

Wer bin ich

GET/v1/me

Gibt den Firmennamen und die Berechtigungen des aufrufenden Schlüssels zurück. Funktioniert mit jedem gültigen Schlüssel, kein Scope erforderlich. Integrationsplattformen nutzen dies, um das verbundene Konto zu benennen.

Anfrage
curl https://api.airbasehq.com/v1/me \
  -H "Authorization: Bearer $API_KEY"
Antwort 200
{
  "company": { "name": "Acme Inc." },
  "apiKey": {
    "name": "Zapier",
    "permissions": {
      "canManageInterviews": true,
      "canManageApplications": true
    }
  }
}

KI-Interviews

Ein KI-Interview ist eine wiederverwendbare Vorauswahl: ein Titel, optionaler Rollenkontext und Fragen. Veröffentlichen Sie es und laden Sie dann beliebig viele Kandidaten ein. Jeder Kandidat erhält einen persönlichen Link, und die KI führt das Gespräch, zeichnet es auf und bewertet es.

Ein KI-Interview erstellen

POST/v1/ai-interviewsInterviews

Erstellt ein eigenständiges KI-Interview, zum Beispiel eines pro Rolle in Ihrem ATS. Lassen Sie die Fragen leer, um sie später in der App zu konfigurieren, oder übergeben Sie bis zu 50.

Body

titlestringErforderlich1 bis 255 Zeichen

Name des Interviews, üblicherweise die Rolle, für die es die Vorauswahl trifft.

contentstringOptionalbis zu 10.000 Zeichen

Rollenkontext, den der KI-Interviewer nutzt, um das Gespräch zu lenken.

questionsstring[]Optionalbis zu 50 Einträge

Die zu stellenden Fragen. Je 1 bis 1.000 Zeichen.

rubricTemplateCodenumberOptional

Scorecard, anhand derer bewertet wird. Standardmäßig die Scorecard Ihres Arbeitsbereichs.

status"draft" | "published"OptionalStandard draft

Nur veröffentlichte Interviews können Kandidaten empfangen.

Anfrage
curl -X POST https://api.airbasehq.com/v1/ai-interviews \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Backend Engineer AI Screen",
    "content": "Screen for Node.js and distributed systems experience.",
    "questions": ["Tell me about a service you built and scaled."],
    "status": "published"
  }'
Antwort 201
{
  "interview": {
    "code": 27,
    "title": "Backend Engineer AI Screen",
    "content": "Screen for Node.js and distributed systems experience.",
    "status": "published",
    "createdAt": "2026-07-01T09:12:00.000Z",
    "updatedAt": "2026-07-01T09:12:00.000Z"
  }
}

KI-Interviews auflisten

GET/v1/ai-interviewsInterviews

Seitenweise Liste Ihrer Interviews mit Kandidatenanzahl. Filtern Sie nach Status oder suchen Sie nach Titel.

Query

pageintegerOptionalStandard 1

Seitenzahl.

perPageintegerOptional1 bis 100, Standard 20

Ergebnisse pro Seite.

statusstringOptional

Eines von draft, published, paused, archived.

searchstringOptional2 bis 500 Zeichen

Titelsuche.

Anfrage
curl "https://api.airbasehq.com/v1/ai-interviews?status=published&perPage=20" \
  -H "Authorization: Bearer $API_KEY"
Antwort 200
{
  "interviews": [
    {
      "code": 27,
      "title": "Backend Engineer AI Screen",
      "status": "published",
      "candidateCount": 42,
      "createdAt": "2026-07-01T09:12:00.000Z",
      "updatedAt": "2026-07-02T14:03:00.000Z"
    }
  ],
  "pagination": { "page": 1, "perPage": 20, "total": 1, "totalPages": 1 }
}

Ein KI-Interview abrufen

GET/v1/ai-interviews/:codeInterviews

Vollständige Details zu einem Interview, einschließlich seiner Fragen, des Kanals, der Dauer und der Scorecard, anhand derer es bewertet. Noch nicht konfigurierte Felder kommen als null zurück.

Path

codeintegerErforderlich

Der Interview-Code aus den Antworten von Erstellen oder Auflisten.

Antwort 200
{
  "interview": {
    "code": 27,
    "title": "Backend Engineer AI Screen",
    "content": "Screen for Node.js and distributed systems experience.",
    "status": "published",
    "questions": ["Tell me about a service you built and scaled."],
    "channel": "audio",
    "duration": 15,
    "scorecard": { "code": 12, "name": "Backend Engineer Scorecard" },
    "createdAt": "2026-07-01T09:12:00.000Z",
    "updatedAt": "2026-07-02T14:03:00.000Z"
  }
}

Kandidaten einladen

POST/v1/ai-interviews/:code/invitationsInterviewsTalent Pool

Lädt bis zu 500 Kandidaten in einem Aufruf ein und gibt für jeden einen persönlichen Interview-Link zurück. Vollständig idempotent: Wird jemand erneut eingeladen, wird sein bestehender Link mit dem Status skipped zurückgegeben, sodass erneute Versuche immer sicher sind. Das Interview muss veröffentlicht sein.

Body

candidatesobject[]Erforderlich1 bis 500 Einträge

Kandidatenobjekte, siehe Felder unten.

Kandidatenobjekt (Hauptfelder)

emailstringOptionalgültige E-Mail

Erforderlich, sofern keine Telefonnummer angegeben ist.

phonestringOptionalE.164 bevorzugt

Erforderlich, sofern keine E-Mail angegeben ist. Erforderlich für Telefoninterviews.

firstName / lastNamestringOptional

Ein Name ist erforderlich, entweder aufgeteilt oder als fullName.

fullNamestringOptional

Alternative zu aufgeteilten Namen. Wird automatisch aufgeteilt.

externalIdstringOptional

Ihre eigene ID für den Kandidaten, wird in den Ergebnissen zurückgegeben.

preferredCommunicationChannel"email" | "sms"Optional

Wie die Einladung zugestellt wird.

country, city, locationstringOptional

Angaben zum Standort.

linkedIn, portfolio, websitestringOptional

Profil-Links.

currentTitle, currentCompanystringOptional

Aktuelle Position.

resumeTextstringOptionalbis zu 200.000 Zeichen

Roher Lebenslauftext. Wird in das Kandidatenprofil eingelesen.

source, referralSourcestringOptional

Attribution. referralSource hat Vorrang, wenn beide gesetzt sind.

experiences, educations, skills, certifications, languagesobject[]Optional

Strukturierte Profildaten. Übergeben Sie, was Sie haben, alles ist optional.

Anfrage
curl -X POST https://api.airbasehq.com/v1/ai-interviews/27/invitations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "candidates": [
      {
        "fullName": "Ada Lovelace",
        "email": "[email protected]",
        "phone": "+14155552671",
        "externalId": "ats-4711"
      }
    ]
  }'
Antwort 200
{
  "summary": {
    "invitedCount": 1,
    "failedCount": 0,
    "skippedCount": 0,
    "phoneDroppedCount": 0
  },
  "candidates": [
    {
      "row": 1,
      "status": "invited",
      "applicationCode": 3,
      "candidateCode": 18,
      "email": "[email protected]",
      "phone": "+14155552671",
      "interviewUrl": "https://interviewer.cuee.ai/<token>",
      "warnings": [],
      "error": null
    }
  ]
}
  • Der Zeilenstatus ist invited, skipped oder failed. Skipped bedeutet, dass der Kandidat bereits eingeladen wurde und derselbe Link zurückgegeben wird.
  • Interview-Links laufen nach etwa 30 Tagen ab.
  • Wenn die Interview-Zustellungseinstellung "Skip, deliver the link yourself" lautet, wird von uns nichts versendet und Sie stellen interviewUrl über Ihren eigenen Kanal zu.

Kandidaten eines Interviews auflisten

GET/v1/ai-interviews/:code/candidatesTalent Pool

Seitenweise Kandidatenliste mit aktueller Stufe, Verarbeitungsstatus und der neuesten Bewertung. Die Sortierung recently_scored gibt nur bewertete Kandidaten zurück, neueste Bewertung zuerst, was das Abfragen nach frischen Ergebnissen zum Einzeiler macht.

Query

pageintegerOptionalStandard 1

Seitenzahl.

perPageintegerOptional1 bis 100, Standard 50

Ergebnisse pro Seite.

sortstringOptionalnewest | recently_scored

Standard newest. Verwenden Sie recently_scored, um nach Ergebnissen abzufragen.

Anfrage
curl "https://api.airbasehq.com/v1/ai-interviews/27/candidates?sort=recently_scored" \
  -H "Authorization: Bearer $API_KEY"
Antwort 200
{
  "candidates": [
    {
      "applicationCode": 3,
      "candidateCode": 18,
      "name": "Ada Lovelace",
      "email": "[email protected]",
      "phone": "+14155552671",
      "currentStage": { "name": "AI Interview", "category": "interview", "orderIndex": 2 },
      "isReady": true,
      "processingStatus": "completed",
      "appliedAt": "2026-07-02T10:00:00.000Z",
      "latestScore": { "overallScore": 82, "scoredAt": "2026-07-02T11:05:00.000Z" },
      "latestInterview": { "interviewStatus": "completed", "channel": "phone" }
    }
  ],
  "pagination": { "page": 1, "perPage": 50, "total": 1, "totalPages": 1 }
}

Ein Kandidatenergebnis abrufen

GET/v1/ai-interviews/:code/candidates/:applicationCodeTalent Pool

Der vollständige Bericht für einen Kandidaten: Profil, Bewertungen je Kriterium mit Belegen, Frage-Antwort-Paare, Interviewversuche und Links zur Aufzeichnung und zum Transkript.

Path

codeintegerErforderlich

Der Interview-Code.

applicationCodeintegerErforderlich

Aus Einladungsantworten, Kandidatenlisten oder Webhooks.

Antwort 200 (gekürzt)
{
  "candidate": {
    "applicationCode": 3,
    "candidateCode": 18,
    "name": "Ada Lovelace",
    "evaluation": {
      "overallScore": 82,
      "summary": "Strong communication and relevant systems experience.",
      "criteria": [
        {
          "criterionName": "Technical depth",
          "score": 85,
          "evidenceCommentary": "Described sharding a Postgres cluster in detail."
        }
      ]
    },
    "interviews": [
      {
        "attemptNumber": 1,
        "interviewStatus": "completed",
        "channel": "phone",
        "cheatStatus": "no_suspicion",
        "recordingUrl": "https://…signed…",
        "transcriptUrl": "https://…signed…"
      }
    ]
  }
}
  • recordingUrl und transcriptUrl sind signierte Links, die nach etwa 4 Stunden ablaufen. Rufen Sie sie zeitnah ab und speichern Sie sie, wenn Sie Ergebnisse archivieren.
  • Beide sind null, bis die Nachbearbeitung des Interviews abgeschlossen ist. Warten Sie auf den interview.scored-Webhook, um zu wissen, dass der Bericht bereit ist.

Talentpool

Ihr Talentpool umfasst jeden Kandidaten, den Ihr Arbeitsbereich je gesehen hat, durchsuchbar mit KI. Fügen Sie Kandidaten direkt hinzu, ohne etwas zu planen, und finden Sie sie später mit natürlicher Sprache.

Den Talentpool durchsuchen

GET/v1/candidatesView Applications

Ein Suchfeld, zwei Verhaltensweisen. Eine E-Mail, ein Name oder eine Telefonnummer wird direkt abgeglichen. Alles andere löst eine semantische Suche über Lebensläufe, Erfahrung, Fähigkeiten und frühere Bewertungen aus, sodass "Senior-React-Entwickler in Berlin" einfach funktioniert.

Query

searchstringOptional2 bis 500 Zeichen

E-Mail, Name, Telefon oder eine natürlichsprachige Abfrage. Weglassen, um die neuesten zuerst aufzulisten.

pageintegerOptionalStandard 1

Seitenzahl.

perPageintegerOptional1 bis 100, Standard 20

Ergebnisse pro Seite.

Anfrage
curl "https://api.airbasehq.com/v1/candidates?search=senior+react+developer+in+berlin" \
  -H "Authorization: Bearer $API_KEY"
Antwort 200
{
  "candidates": [
    {
      "candidateCode": 18,
      "name": "Ada Lovelace",
      "email": "[email protected]",
      "profileSummary": "Senior engineer, 8 years across fintech and infra.",
      "applicationCount": 2,
      "applications": [
        {
          "applicationCode": 3,
          "jobTitle": "Backend Engineer",
          "stageName": "AI Interview",
          "averageScore": 82
        }
      ],
      "match": {
        "distance": 0.18,
        "type": "semantic",
        "text": "Led a 6 person platform team building React tooling in Berlin."
      }
    }
  ],
  "pagination": { "page": 1, "perPage": 20, "total": 1, "totalPages": 1 }
}
  • match erscheint nur bei semantischen Treffern. distance ist die Kosinus-Distanz, niedriger bedeutet näher.

Einen Kandidaten hinzufügen

POST/v1/candidatesTalent Pool

Fügt einen Kandidaten zum Talentpool hinzu und macht ihn sofort durchsuchbar. Dedupliziert nach E-Mail und Telefon: Wird jemand erneut hinzugefügt, werden fehlende Felder ergänzt und bestehende niemals überschrieben, sodass das Synchronisieren aus einem anderen System sicher ist.

Body

…candidate fieldsobjectOptional

Dasselbe Kandidatenobjekt wie bei Einladungen: Name plus E-Mail oder Telefon erforderlich, alles andere optional.

aboutCandidatestringOptionalbis zu 10.000 Zeichen

Freie Notizen, die im Profil gespeichert werden.

Anfrage
curl -X POST https://api.airbasehq.com/v1/candidates \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Grace Hopper",
    "email": "[email protected]",
    "currentTitle": "Staff Engineer",
    "resumeText": "…"
  }'
Antwort 201
{
  "candidate": {
    "candidateCode": 19,
    "status": "created",
    "name": "Grace Hopper",
    "email": "[email protected]",
    "phone": null
  }
}
  • Gibt 201 mit Status created für neue Kandidaten zurück oder 200 mit Status existing bei Deduplizierung.

Webhooks

Webhooks senden Ereignisse in dem Moment an Ihren Server, in dem sie passieren, sodass Sie niemals abfragen müssen. Abonnieren Sie mit einer https-URL die Ereignisse, die Sie interessieren, und verifizieren Sie jede Zustellung mit ihrer Signatur.

Das wichtigste Ereignis ist interview.scored: Es wird ausgelöst, wenn der Bericht tatsächlich bereit ist, inklusive Bewertungen und Zusammenfassung, nicht nur, wenn das Gespräch endet.

EreignisWird ausgelöst, wenn
interview.scoredDer KI-Bericht ist bereit: Gesamtbewertung, Zusammenfassung und Kriterien sind verfügbar.
interview.completedDie Interview-Sitzung ist beendet. Die Bewertung läuft möglicherweise noch.
interview.startedDer Kandidat hat das Interview begonnen.
interview.cancelledDas Interview wurde abgebrochen.
candidate.appliedEin Kandidat hat sich über einen geteilten Interview-Link selbst registriert.
candidate.invitedEin Kandidat wurde eingeladen, per API oder durch einen Recruiter.

Einen Webhook erstellen

POST/v1/webhooksView Applications

Abonniert einen https-Endpunkt für ein oder mehrere Ereignisse. Das Signaturgeheimnis wird nur einmal zurückgegeben, ausschließlich bei der Erstellung. Sie können bis zu 10 aktive Webhooks halten.

Body

urlstringErforderlichnur https

Ihr Endpunkt. Private und interne Adressen werden abgelehnt.

eventsstring[]Erforderlich1 bis 6 Einträge

Ereignisse aus dem obigen Katalog.

Anfrage
curl -X POST https://api.airbasehq.com/v1/webhooks \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/airbase",
    "events": ["interview.scored", "candidate.applied"]
  }'
Antwort 201
{
  "webhook": {
    "id": "3e1f1a3e-1111-2222-3333-444455556666",
    "url": "https://example.com/hooks/airbase",
    "events": ["interview.scored", "candidate.applied"],
    "status": "active",
    "secret": "whsec_<64 hex chars>",
    "createdAt": "2026-07-01T09:12:00.000Z"
  }
}

Webhooks auflisten

GET/v1/webhooksView Applications

Alle Abonnements mit Zustellungszustand: aufeinanderfolgende Fehler sowie der letzte Erfolg und Fehler. Geheimnisse werden niemals erneut zurückgegeben.

Antwort 200
{
  "webhooks": [
    {
      "id": "3e1f1a3e-1111-2222-3333-444455556666",
      "url": "https://example.com/hooks/airbase",
      "events": ["interview.scored"],
      "status": "active",
      "consecutiveFailures": 0,
      "lastSuccessAt": "2026-07-02T11:05:03.000Z",
      "lastFailureAt": null,
      "lastFailureReason": null,
      "createdAt": "2026-07-01T09:12:00.000Z"
    }
  ]
}

Einen Webhook löschen

DELETE/v1/webhooks/:webhookIdView Applications

Meldet den Endpunkt ab. Zustellungen stoppen sofort.

Path

webhookIduuidErforderlich

Die Webhook-ID aus Erstellen oder Auflisten.

Antwort 200
{ "deleted": true }

Zustellungen empfangen

Jede Zustellung ist ein POST an Ihre URL mit dem Ereignisnamen im Header X-Cuee-Event und einem signierten Body. Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen 2xx. Alles andere wird mit exponentiellem Backoff erneut versucht, und nach 20 aufeinanderfolgenden Fehlern wird das Abonnement deaktiviert, was Sie in der Webhook-Liste sehen können.

Zustellung für interview.scored
POST /hooks/airbase HTTP/1.1
Content-Type: application/json
User-Agent: Cuee-Webhooks/1.0
X-Cuee-Event: interview.scored
X-Cuee-Delivery: 8b7f4c2a-9d3e-4f5a-b6c7-d8e9f0a1b2c3
X-Cuee-Signature: t=1751449503,v1=<hex hmac>

{
  "id": "8b7f4c2a-9d3e-4f5a-b6c7-d8e9f0a1b2c3",
  "event": "interview.scored",
  "createdAt": "2026-07-02T11:05:03.000Z",
  "data": {
    "interviewCode": 27,
    "jobTitle": "Backend Engineer AI Screen",
    "applicationCode": 3,
    "candidateCode": 18,
    "name": "Ada Lovelace",
    "email": "[email protected]",
    "phone": "+14155552671",
    "interviewStatus": "completed",
    "channel": "phone",
    "completedAt": "2026-07-02T10:58:41.000Z",
    "cheatStatus": "no_suspicion",
    "overallScore": 82,
    "summary": "Strong communication and relevant systems experience.",
    "summaryBullets": ["8 years backend experience", "Clear system design answers"],
    "scoredAt": "2026-07-02T11:05:00.000Z"
  }
}
Die Signatur verifizieren (Node.js)
import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(rawBody, signatureHeader, secret) {
  const { t, v1 } = Object.fromEntries(
    signatureHeader.split(',').map((part) => part.split('='))
  )
  // Reject replays older than 5 minutes
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false

  const expected = createHmac('sha256', secret)
    .update(t + '.' + rawBody)
    .digest('hex')
  return timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}
  • Die Signatur ist ein HMAC SHA-256 von "<timestamp>.<raw body>" mit Ihrem whsec-Geheimnis. Verifizieren Sie immer gegen den rohen Anfragetext, vor jeglichem JSON-Parsing.
  • Payloads enthalten nur numerische Codes, niemals interne IDs. Verwenden Sie applicationCode mit dem Endpunkt für Kandidatenergebnisse, um den vollständigen Bericht abzurufen.