Webhooks
Erhalten Sie in Echtzeit Benachrichtigungen über den Lebenszyklus und die Antworten eines Crowd Jobs — Webhooks konfigurieren, Zustellungen verifizieren und Wiederholungsversuche verwalten.
Ein Crowd-Job-Webhook ist ein ausgehender HTTP-Callback, den Sie konfigurieren, um einen Ihrer eigenen Endpunkte zu benachrichtigen, sobald bei einem Job etwas passiert — eine Statusänderung oder eine eingegangene, akzeptierte oder abgelehnte Worker-Antwort. Ein einzelner Job kann beliebig viele unabhängige Webhooks haben, jeder mit einer eigenen Teilmenge abonnierter Ereignistypen, sodass Sie unterschiedliche Ereignisse an unterschiedliche Systeme weiterleiten können (zum Beispiel Lebenszyklus-Ereignisse an eine Slack-Integration und vollständige Antwort-Payloads an Ihre eigene Datenpipeline).
Webhooks werden pro Job konfiguriert, entweder bei der Erstellung oder nachträglich, und können sowohl über die API als auch über den Tab Webhooks des Jobs in der Plattform-UI verwaltet werden.
Ereignistypen
| Ereignis | Tritt ein, wenn |
|---|---|
job.created | Der Job erstellt wird. |
job.paused | Der Job manuell pausiert wird. |
job.resumed | Ein pausierter Job manuell fortgesetzt wird. |
job.no_tasks | Jeder verbleibende Slot entweder gerade beansprucht ist oder auf eine Antwort wartet, die noch überprüft werden muss — aktuell ist nichts beanspruchbar, der Job ist aber noch nicht abgeschlossen. |
job.finished | Die Gesamtzahl der akzeptierten (und zur Überprüfung ausstehenden) Antworten maxSlots erreicht. |
job.expired | validUntil verstreicht, während der Job noch zuweisbar war. |
job.archived | Der Job archiviert (soft-gelöscht) wird. |
job.extended | maxRepetitionsPerVariant über den Extend-Endpunkt erhöht wird. |
answer.received | Ein Worker eine Antwort einreicht. Schlanke Payload — nur Kennungen. |
answer.received.full | Derselbe Zeitpunkt wie answer.received, jedoch mit der vollständigen Antwort-Payload — identisch im Aufbau zu einer Zeile des JSON-Antwortexports. |
answer.accepted | Eine Antwort durch manuelle Prüfung akzeptiert wird — über den Accept-Endpunkt oder die Option "alle ausstehenden akzeptieren" beim Archivieren eines Jobs. |
answer.rejected | Eine Antwort abgelehnt wird. |
answer.accepted wird ausschließlich bei manuell geprüften Akzeptierungen ausgelöst. Es wird nicht ausgelöst, wenn eine Antwort sofort bei der Einreichung akzeptiert wurde (Qualifikationstest-Jobs oder ein Survey-Job mit autoAcceptHours: 0), noch für den automatischen Sweep, der eine ausstehende Antwort akzeptiert, sobald autoAcceptHours verstrichen ist. Falls Sie auch darüber informiert werden möchten, abonnieren Sie stattdessen answer.received/answer.received.full und verfolgen den Prüfstatus selbst, oder fragen Sie GET /v2/crowd-jobs/:jobId/answers per Polling ab.
answer.received und answer.received.full sind unabhängige Abonnements, nicht zwei Schweregrade desselben Ereignisses — abonnieren Sie, was immer Sie benötigen (auch beides). Verwenden Sie die schlanke Variante, wenn Sie nur wissen müssen, dass etwas passiert ist, und die Details später selbst abrufen; verwenden Sie die vollständige Variante, wenn die komplette Antwort sofort an Sie gesendet werden soll.
Einen Webhook erstellen
Webhook-URLs müssen https:// verwenden und öffentlich aus dem Internet erreichbar sein — Crowdee lehnt URLs ab, die zu einer privaten, Loopback- oder Link-Local-Adresse auflösen (zum Beispiel 127.0.0.1, 10.0.0.0/8 oder die verbreitete Cloud-Metadaten-Adresse 169.254.169.254) — als Sicherheitsmaßnahme.
curl -X POST https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks \
-H "X-API-Key: crw_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/crowdee",
"description": "Slack notifier",
"eventTypes": ["job.finished", "job.archived", "answer.accepted", "answer.rejected"]
}'Die 201 Created-Antwort enthält das Webhook-Objekt und dessen Signatur-Secret im Klartext:
{
"id": "whk_a1b2c3d4e5f6g7h8i9j0k1l2",
"jobId": "job_abc123def456ghi789jkl01",
"url": "https://example.com/webhooks/crowdee",
"description": "Slack notifier",
"eventTypes": ["job.finished", "job.archived", "answer.accepted", "answer.rejected"],
"status": "active",
"secret": "whsec_9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c"
}Das Feld secret wird nur in dieser Antwort zurückgegeben (und erneut, falls Sie es später rotieren) — Crowdee speichert das Secret nicht im Klartext und kann es Ihnen nicht erneut anzeigen. Speichern Sie es sofort in einem Secrets-Manager. Falls Sie es verlieren, rotieren Sie den Webhook, um ein neues zu erzeugen — siehe Webhooks verwalten.
Sie können auch bereits bei der Job-Erstellung einen oder mehrere Webhooks anlegen, indem Sie ein webhooks-Array in die Payload zur Job-Erstellung aufnehmen:
{
"name": "Q3 Image Authenticity Review",
"title": "...",
"description": "...",
"surveyTemplateVersionId": "tmplv_abc123def456ghi789jkl01",
"webhooks": [
{ "url": "https://example.com/webhooks/crowdee", "eventTypes": ["job.finished", "answer.accepted"] }
]
}Die Antwort auf die Job-Erstellung enthält dann zusätzlich zu den Feldern des erstellten Jobs ein webhooks-Array, wobei jeder Eintrag sein eigenes einmaliges secret trägt.
Payload und Zustellung
Jede Zustellung ist ein HTTP-POST mit einem Content-Type: application/json-Body. Ereignistyp und Zustellungs-Identität werden in Headern übertragen, nicht im Body — der Body ist einfach das reine Payload-Objekt für dieses Ereignis:
| Header | Beschreibung |
|---|---|
X-Crowdee-Event | Der Ereignistyp, z. B. answer.rejected. |
X-Crowdee-Delivery-Id | Eindeutige ID dieser Zustellung. Bleibt über automatische Wiederholungsversuche derselben Zustellung hinweg stabil — eine manuelle erneute Zustellung erhält eine neue ID. Nutzen Sie sie zur Deduplizierung, falls Ihr Endpunkt dieselbe Zustellung mehrfach erhalten könnte. |
X-Crowdee-Webhook-Id | Die ID der Webhook-Konfiguration, die diese Zustellung gesendet hat. |
X-Crowdee-Signature | HMAC-Signatur — siehe Signatur verifizieren. |
Job-Lebenszyklus-Ereignisse (job.created, job.paused, job.resumed, job.archived) teilen sich diesen Aufbau:
{
"jobId": "job_abc123def456ghi789jkl01",
"title": "Assess whether this image has been digitally manipulated",
"status": "archived",
"category": "survey",
"projectId": "proj_xyz987stu654rqp321onm09"
}job.finished, job.no_tasks und job.expired enthalten zusätzlich den Status, aus dem der Job übergegangen ist:
{
"jobId": "job_abc123def456ghi789jkl01",
"title": "...",
"status": "finished",
"category": "survey",
"projectId": "proj_xyz987stu654rqp321onm09",
"previousStatus": "no_tasks"
}job.extended enthält zusätzlich die Höhe der Erweiterung:
{
"jobId": "job_abc123def456ghi789jkl01",
"title": "...",
"status": "assignable",
"category": "survey",
"projectId": "proj_xyz987stu654rqp321onm09",
"additionalRepetitions": 5,
"newMaxRepetitionsPerVariant": 15
}answer.received (schlank):
{
"answerId": "ans_abc123",
"jobId": "job_abc123def456ghi789jkl01",
"taskId": "tsk_xyz987",
"workerId": "usr_ghi789",
"guestWorkerId": null,
"submittedAt": "2025-09-14T10:23:41Z",
"variantIndex": 7
}answer.received.full — identisch im Aufbau zu einer Zeile aus dem JSON-Antwortexport, einschließlich des Survey-answers-Objekts, der Worker-Metadaten und des Input-Data-Snapshots. feedback ist hier immer null, da etwaiges Worker-Feedback erst nach diesem Zeitpunkt eingereicht wird.
answer.accepted / answer.rejected:
{
"answerId": "ans_abc123",
"jobId": "job_abc123def456ghi789jkl01",
"taskId": "tsk_xyz987",
"workerId": "usr_ghi789",
"guestWorkerId": null,
"reviewedById": "usr_reviewer01",
"rejectionReason": "Response does not address the required criteria.",
"rejectionCategory": "failed_instructions"
}(rejectionReason/rejectionCategory sind bei answer.accepted null; stattdessen wird acceptanceNote mitgeliefert, sofern eine angegeben wurde.)
Signatur verifizieren
X-Crowdee-Signature folgt demselben Schema, das auch Stripe verwendet: t=<unix_timestamp>,v1=<hex_hmac_sha256>. Die Signatur ist ein HMAC-SHA256 über den String {timestamp}.{roher Request-Body}, mit dem Secret Ihres Webhooks als Schlüssel.
const crypto = require("node:crypto");
function isValidCrowdeeSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => part.split("=")),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(parts.v1, "hex"),
Buffer.from(expected, "hex"),
);
}Berechnen Sie den HMAC über den rohen, unverarbeiteten Request-Body — nicht über eine neu serialisierte Version des geparsten JSON, die sich in Leerzeichen oder Schlüsselreihenfolge unterscheiden kann und dann nicht übereinstimmt. Lehnen Sie jede Zustellung ab, deren Signatur nicht übereinstimmt, und behandeln Sie t als Hinweis, um Zustellungen abzulehnen, die älter sind als das für Ihre Integration sinnvolle Zeitfenster — Crowdee selbst erzwingt kein solches Zeitfenster.
Wiederholungsversuche, Zustellungsprotokoll und erneute Zustellung
Eine Zustellung, die keine 2xx-Antwort erhält, wird automatisch mit exponentiellem Backoff wiederholt, bis zu 8 Versuche über einen Zeitraum von etwa 2 Stunden verteilt — lang genug, um einen kurzzeitigen Ausfall auf Ihrer Seite zu überstehen, ohne dass Sie eingreifen müssen. Weiterleitungen (3xx) werden als Fehlschlag behandelt statt ihnen zu folgen.
Aktuelle Zustellungen eines Webhooks ansehen:
curl "https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId}/deliveries?limit=50" \
-H "X-API-Key: crw_YOUR_API_KEY"{
"total": 12,
"items": [
{
"id": "whd_1a2b3c4d5e6f7g8h9i0j1k2l",
"event": "answer.rejected",
"status": "succeeded",
"attempt": 1,
"responseStatusCode": 200,
"durationMs": 184,
"createdAt": "2025-09-14T10:23:42Z",
"deliveredAt": "2025-09-14T10:23:42Z"
}
]
}status ist entweder pending, succeeded, failed (wird wiederholt) oder exhausted (alle Versuche sind fehlgeschlagen). Eine erschöpfte (oder anderweitig fehlgeschlagene) Zustellung manuell erneut anstoßen:
curl -X POST https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver \
-H "X-API-Key: crw_YOUR_API_KEY"Die erneute Zustellung erzeugt eine neue Zustellung mit eigener ID und eigenem Budget von 8 Versuchen — die ID und der Versuchszähler der ursprünglichen Zustellung werden nicht wiederverwendet.
Automatische Deaktivierung
Sammelt ein Webhook 10 aufeinanderfolgende vollständig erschöpfte Zustellungen an — jeder Wiederholungsversuch aller zehn Zustellungen ist fehlgeschlagen, ohne dass dazwischen eine erfolgreiche Zustellung lag —, setzt Crowdee ihn automatisch auf status: "disabled" und benachrichtigt die Watcher und Reviewer des Jobs per E-Mail. Ein deaktivierter Webhook erhält keine neuen Zustellungen mehr, bis Sie ihn wieder aktivieren.
Mit einer Update-Anfrage wieder aktivieren:
curl -X PUT https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId} \
-H "X-API-Key: crw_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }'Die Reaktivierung setzt den Zähler aufeinanderfolgender Fehlschläge auf null zurück — es braucht also wieder zehn in Folge, bevor der Webhook erneut automatisch deaktiviert würde.
Bevor Sie reaktivieren, nutzen Sie Ein Testereignis senden, um zu bestätigen, dass Ihr Endpunkt wieder erreichbar ist — andernfalls könnte der nächste Schwung echter Ereignisse den Zähler einfach wieder auf zehn hochtreiben.
Webhooks verwalten
Webhooks eines Jobs auflisten:
GET https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks
X-API-Key: crw_YOUR_API_KEYEinen Webhook aktualisieren — URL, Beschreibung, abonnierte Ereignisse oder Status ändern:
curl -X PUT https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId} \
-H "X-API-Key: crw_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "eventTypes": ["job.finished", "job.archived"] }'Signatur-Secret rotieren — invalidiert das alte Secret sofort und liefert ein neues zurück (einmalig angezeigt, genau wie bei der Erstellung):
curl -X POST https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId}/rotate-secret \
-H "X-API-Key: crw_YOUR_API_KEY"Ein Testereignis senden — liefert sofort und synchron eine künstliche webhook.test-Payload aus (wird nicht wiederholt und nicht im Zustellungsprotokoll erfasst), sodass Sie beim Einrichten Ihres Endpunkts ein sofortiges Erfolgs-/Fehlschlagsergebnis erhalten:
curl -X POST https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId}/test \
-H "X-API-Key: crw_YOUR_API_KEY"{ "ok": true, "statusCode": 200, "errorMessage": null, "durationMs": 142 }Einen Webhook löschen — er erhält danach keine weiteren Zustellungen mehr. Dies kann nicht rückgängig gemacht werden; legen Sie ihn bei Bedarf neu an (und aktualisieren Sie das Secret auf der Empfängerseite):
curl -X DELETE https://api.crowdee.ai/v2/crowd-jobs/{jobId}/webhooks/{webhookId} \
-H "X-API-Key: crw_YOUR_API_KEY"All dies steht auch über die Plattform-UI im Tab Webhooks eines Jobs zur Verfügung — Webhooks erstellen/bearbeiten/löschen, Secrets rotieren, Testereignisse senden sowie das Zustellungsprotokoll durchsuchen oder erneut zustellen, ganz ohne die API direkt anzusprechen.
Wie hilfreich ist diese Seite?
Antworten
Crowd-Worker-Antworten überprüfen, akzeptieren und exportieren — und in Datensätze oder Eingabedaten für weitere Pipeline-Runs umwandeln.
Aufgabenvorlagen
SurveyJS-basierte Aufgabendefinitionen, die die Fragen strukturieren, welche Crowdworker während der Verifizierung beantworten.