Webhooks

Aufbau, Signatur und Zustellung der Webhook-Requests für Antworten und abgeschlossene Teilnahmen.

PollHero sendet pro Event einen signierten POST-Request mit JSON-Body an die Endpoint-URL deines Projekts. So landen Antworten ohne eigene Abfrage in deinem CRM, deinem Data Warehouse, in Slack oder in einem Automatisierungstool wie Zapier, Make oder n8n. Die übrige Data Export API holt Daten auf Anfrage ab, Webhooks bringen sie dir, sobald sie entstehen.

EigenschaftWert
VerfügbarkeitEnterprise-Plan
EbeneProjekt, gilt für alle Umfragen des Projekts
Eventsquestion_answered, survey_completed
FormatPOST mit JSON-Body, UTF-8
VerifizierungToken-Header und HMAC-SHA256-Signatur

Webhook einrichten

Webhooks richtest du pro Projekt ein. Die Einstellungen können Inhaber des Accounts und Admins des Projekts ändern.

Schritt 1: Öffne die Projekteinstellungen, scrolle zum Abschnitt Webhook und setze den Toggle Webhook aktivieren auf AN.

Schritt 2: Aktiviere eines oder beide Events: Antworten senden (Event question_answered) und Abschluss der Umfrage senden (Event survey_completed). Unter jedem aktiven Event erscheint ein Feld Endpoint-URL. Trag dort die URL deines Empfängers ein. Beide Events können an dieselbe oder an verschiedene URLs gehen.

Schritt 3: Klick neben dem Feld auf Test-Request senden. PollHero schickt einen Beispiel-Request mit "test": true an die eingetragene URL und zeigt dir an, ob dein Endpoint erfolgreich geantwortet hat. Der Test funktioniert schon vor dem Speichern.

Schritt 4: Speichere die Projekteinstellungen. Beim ersten Speichern mit aktivem Event erzeugt PollHero den Verification Token und das Signing Secret. Beide findest du danach im Abschnitt Verifizierung und kannst sie mit Kopieren übernehmen.

Solange Webhook aktivieren auf AUS steht, wird nichts gesendet, auch wenn einzelne Events aktiv sind.

Anforderungen an den Endpoint

  • Die URL muss öffentlich über http oder https erreichbar sein. Adressen in privaten Netzen sowie Hostnamen wie localhost lehnt PollHero ab.
  • Der Endpoint muss innerhalb von 15 Sekunden mit einem Status 2xx antworten. Für den Verbindungsaufbau gelten 5 Sekunden.
  • Weiterleitungen folgt PollHero nicht. Ein Status 301 oder 302 zählt als fehlgeschlagene Zustellung.
  • Antworte am besten sofort mit 200 und verarbeite den Inhalt danach im Hintergrund.

Events

EventWird gesendetInhalt
question_answeredsobald eine Antwort auf eine Frage gespeichert wurdedie eine Antwort mit Frage, Teilnahme und Teilnehmenden
survey_completedsobald eine Teilnahme abgeschlossen istalle Antworten der Teilnahme
  • Übersprungene Fragen lösen kein Event aus.
  • Ändert jemand innerhalb derselben Teilnahme eine Antwort, wird question_answered erneut gesendet, mit neuer messageId.
  • survey_completed kommt einmal pro Teilnahme.
  • Ist PII Masking aktiv, kommen die Antworten bereits maskiert an.
  • Antworten aus dem CSV-Import lösen keine Events aus.
  • Die Stimmungsanalyse läuft erst nach dem Versand. Das Sentiment einer Freitextantwort bekommst du über die Data Export API.

Aufbau des Requests

HeaderInhalt
Content-Typeapplication/json
User-AgentPollHero-Webhook/1.0
X-PollHero-Eventquestion_answered oder survey_completed
X-PollHero-DeliveryID der Nachricht, identisch mit messageId im Body. Bleibt bei Wiederholungen gleich.
X-PollHero-TokenVerification Token des Projekts
X-PollHero-SignatureHMAC-SHA256 des Request-Bodys, siehe Signatur prüfen

Gemeinsame Felder

Jeder Body beginnt mit denselben Feldern:

FeldTypBeschreibung
messageIdStringEindeutige ID der Nachricht. Bleibt bei Wiederholungen gleich, damit du Duplikate erkennst.
deliveryTriesIntegerNummer des Zustellversuchs, beginnend bei 1.
eventStringquestion_answered oder survey_completed
timestampStringZeitpunkt des Events, ISO 8601 in UTC
testBooleanNur im Test-Request enthalten, dort true.

Event question_answered

{
  "messageId": "0e0938c0-1e85-413b-8ffd-5a0e3a26340a",
  "deliveryTries": 1,
  "event": "question_answered",
  "timestamp": "2026-10-27T11:51:20.000000Z",
  "survey": {
    "id": "21cba834-ad20-4f3b-929d-5abfff8c96ae",
    "name": "Post-Purchase-Umfrage"
  },
  "question": {
    "id": "1165ac0a-1fcf-451a-a361-db75602d1283",
    "body": "Wie wahrscheinlich ist es, dass du uns weiterempfiehlst?",
    "type": "nps"
  },
  "response": {
    "user_id": "23decc90-820a-40bf-92ea-db493c71bfa0",
    "collected_at": "2026-10-27T11:51:19.000000Z",
    "completed": false,
    "url": "https://shop.example.com/checkout/thank-you",
    "order_id": "1232"
  },
  "answer": {
    "answer": { "content": 9, "tag": "promoter" }
  },
  "respondent": {
    "id": "23decc90-820a-40bf-92ea-db493c71bfa0",
    "attributes": {
      "email": "jane@example.com",
      "customer_type": "returning",
      "total_purchases": 3,
      "plan": "pro"
    }
  }
}
FeldBeschreibung
survey.id, survey.nameID und Name der Umfrage. Die ID ist dieselbe wie in der Data Export API.
question.idID der Frage, wie in der Data Export API
question.bodyFragetext
question.typeFragetyp in der Schreibweise der API, zum Beispiel nps, single, text
responseDie Teilnahme, zu der die Antwort gehört, siehe unten
answerDie Antwort. answer enthält einen einzelnen Wert, answers eine Liste, zum Beispiel bei Multi Select, Ranking und Matrix. Der Aufbau je Fragetyp entspricht der API, siehe Antworten nach Fragetyp.
respondentDie teilnehmende Person, siehe unten

Event survey_completed

{
  "messageId": "7c1d2a9e-5b7f-4d0e-9a3c-1f2e3d4c5b6a",
  "deliveryTries": 1,
  "event": "survey_completed",
  "timestamp": "2026-10-27T11:52:01.000000Z",
  "survey": {
    "id": "21cba834-ad20-4f3b-929d-5abfff8c96ae",
    "name": "Post-Purchase-Umfrage"
  },
  "response": {
    "user_id": "23decc90-820a-40bf-92ea-db493c71bfa0",
    "collected_at": "2026-10-27T11:51:19.000000Z",
    "completed": true,
    "url": "https://shop.example.com/checkout/thank-you",
    "order_id": "1232",
    "completed_at": "2026-10-27T11:52:01.000000Z",
    "time_taken": 42,
    "device_type": "Desktop",
    "browser": "Chrome",
    "country": "Germany",
    "utm": {
      "source": "newsletter",
      "medium": "email",
      "campaign": "autumn",
      "term": null,
      "content": null
    }
  },
  "answers": [
    {
      "question_id": "1165ac0a-1fcf-451a-a361-db75602d1283",
      "question_type": "nps",
      "question_body": "Wie wahrscheinlich ist es, dass du uns weiterempfiehlst?",
      "answer": { "content": 9, "tag": "promoter" }
    },
    {
      "question_id": "8d2f6b1c-3a4e-4f5d-9c7b-2e1f0a9b8c7d",
      "question_type": "text",
      "question_body": "Was ist der Grund für deine Bewertung?",
      "answer": "Schnelle Lieferung, gerne wieder."
    }
  ],
  "respondent": {
    "id": "23decc90-820a-40bf-92ea-db493c71bfa0",
    "attributes": {
      "email": "jane@example.com",
      "customer_type": "returning",
      "total_purchases": 3,
      "plan": "pro"
    }
  }
}

answers enthält alle beantworteten Fragen in der Reihenfolge, in der sie beantwortet wurden. Jeder Eintrag trägt zusätzlich question_id, question_type und question_body.

Objekt response

FeldTypBeschreibung
user_idStringAnonyme PollHero-User-ID des Browsers, identisch mit respondent.id
collected_atStringZeitpunkt der ersten Antwort in dieser Teilnahme
completedBooleanDie Teilnahme ist abgeschlossen.
urlString oder nullSeite, auf der die Umfrage beantwortet wurde
order_idString oder nullOrder-ID (transaction_id) der Bestellung, die zu dieser Teilnahme gehört, siehe Purchase-Tracking. null, wenn es keine passende Bestellung gibt.

Nur bei survey_completed:

FeldTypBeschreibung
completed_atStringZeitpunkt des Abschlusses
time_takenInteger oder nullDauer der Teilnahme in Sekunden
device_type, browser, countryString oder nullGerät, Browser und Land der teilnehmenden Person
utmObjektsource, medium, campaign, term, content der Seite, auf der die Umfrage angezeigt wurde

Die Bestellung wird über die PollHero-User-ID zugeordnet: Es zählt die Bestellung derselben Person im selben Projekt, die zeitlich am nächsten am Beginn der Teilnahme liegt, höchstens eine Stunde davor oder danach. Meldet dein Shop die Bestellung erst nach der ersten Antwort, kann order_id bei question_answered noch null sein, während survey_completed und die Data Export API sie bereits enthalten.

Objekt respondent

FeldTypBeschreibung
idStringAnonyme PollHero-User-ID
attributesObjektDie Benutzerattribute der Person: die festen Attribute email, customer_id, customer_type, total_revenue, total_purchases, total_aov, country und language, sofern gesetzt, plus alle eigenen Attribute aus identify. Ohne Attribute ist das Objekt leer.

Anders als in der Data Export API bekommst du hier immer alle Attribute, ohne sie anzufordern, und als Objekt mit den Attributnamen als Schlüssel.

Signatur prüfen

Jeder Request trägt zwei Merkmale, an denen dein Endpoint erkennt, dass er wirklich von PollHero kommt:

  • X-PollHero-Token enthält den Verification Token des Projekts. Vergleiche ihn mit dem Wert aus den Projekteinstellungen.
  • X-PollHero-Signature enthält den HMAC-SHA256 des unveränderten Request-Bodys, berechnet mit dem Signing Secret, als Hexadezimalzahl in Kleinbuchstaben.

Berechne die Signatur über die rohen Bytes des Bodys, bevor du das JSON parst. Ein neu serialisiertes JSON ergibt eine andere Signatur. Vergleiche mit einer zeitkonstanten Funktion.

// Node.js mit Express: den rohen Body behalten
import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/pollhero-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto
    .createHmac("sha256", process.env.POLLHERO_SIGNING_SECRET)
    .update(req.body)
    .digest("hex");
  const given = req.get("X-PollHero-Signature") ?? "";

  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const payload = JSON.parse(req.body);
  // Verarbeitung im Hintergrund anstoßen ...
  res.sendStatus(200);
});
<?php
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, getenv('POLLHERO_SIGNING_SECRET'));
$given = $_SERVER['HTTP_X_POLLHERO_SIGNATURE'] ?? '';

if (! hash_equals($expected, $given)) {
    http_response_code(401);
    exit;
}

$payload = json_decode($body, true);
http_response_code(200);

Mit Neu generieren im Abschnitt Verifizierung erzeugst du neuen Token und neues Secret. Die alten Werte gelten ab diesem Moment nicht mehr, dein Empfänger muss die neuen Werte sofort bekommen.

Zustellung und Wiederholungen

  • Eine Zustellung gilt als erfolgreich, wenn dein Endpoint mit Status 2xx antwortet. Jeder andere Status, ein Timeout oder ein Verbindungsfehler zählt als Fehlschlag.
  • Nach einem Fehlschlag versucht PollHero es bis zu vier weitere Male: nach 1, 5, 15 und 60 Minuten. deliveryTries zählt die Versuche, messageId bleibt gleich. Danach wird die Nachricht verworfen.
  • Die Reihenfolge ist nicht garantiert. Zwei Antworten können in anderer Reihenfolge ankommen, als sie gegeben wurden. Nutze timestamp oder collected_at, wenn die Reihenfolge wichtig ist.
  • Webhooks laufen asynchron. In der Regel kommt der Request wenige Sekunden nach der Antwort an.

Schlagen 100 Zustellversuche in Folge fehl, schaltet PollHero den Webhook des Projekts automatisch ab. In den Projekteinstellungen siehst du dann einen Hinweis mit dem Zeitpunkt. Events aus der Zeit der Abschaltung werden nicht nachgesendet. Prüfe deinen Endpoint und setze Webhook aktivieren danach wieder auf AN.

Tipps für den Empfänger

  • Beantworte jeden Request sofort mit 200 und verarbeite die Daten anschließend. So vermeidest du Timeouts und Wiederholungen.
  • Speichere messageId und ignoriere Nachrichten, die du schon kennst. Bei Wiederholungen kann dieselbe Nachricht mehr als einmal ankommen.
  • Für die vollständige Historie oder einen Abgleich nutze die Data Export API. Webhooks liefern nur Ereignisse ab dem Zeitpunkt der Aktivierung.

Auf dieser Seite