Developers
Send responses to your own server with webhooks
Receive every response as a signed JSON payload, and check that it came from StrawPoll.
A webhook sends each response to an address on your own server, as a POST request with a JSON body.
Set it up
- Open Integrations in your dashboard, click Add an integration and choose Webhooks.
- Enter your endpoint’s URL. It has to start with
https://and be reachable from the internet. - Optional: add headers your endpoint expects, for example an
Authorizationheader. - Choose the poll and whether to send new responses, changed responses or both, then save.
- Copy the Signing secret from the integration’s page, then click Send test.
The payload
{
"id": "0193f1c2-…",
"event": "response.created",
"createdAt": "2026-11-08T14:05:12.000Z",
"form": {
"id": "…",
"slug": "team-lunch",
"name": "Team lunch",
"title": "Where should we eat on Friday?",
"url": "https://strawpoll.com/…"
},
"response": {
"id": "…",
"createdAt": "2026-11-08T14:05:11.000Z",
"updatedAt": null,
"deletedAt": null,
"respondent": { "name": "Sam", "email": "sam@example.com", "phone": null },
"answers": [
{
"fieldId": "…",
"question": "Q1 · Where should we eat?",
"value": "…",
"text": "Pizza"
}
],
"comment": null
}
}eventisresponse.created,response.updated,response.deletedorpingfor Send test.- For
response.deleted,deletedAtsays when the respondent deleted their response andanswersholds what they had sent. Remove it from your own records too. idis unique for every delivery. Use it to ignore a delivery you’ve already handled.answerslists every question, in order.textis the answer as it reads in your results;valueis the raw value, whose shape depends on the question type.
Headers
| Header | What’s in it |
|---|---|
X-StrawPoll-Event | The event, like response.created |
X-StrawPoll-Delivery | The delivery’s id |
X-StrawPoll-Timestamp | When it was sent, in Unix seconds |
X-StrawPoll-Signature | sha256= and the signature |
Check the signature
The signature is an HMAC-SHA256, made with your signing secret, of the delivery ID, the timestamp and the raw request body joined by dots. Compute it yourself and compare:
import { createHmac, timingSafeEqual } from 'node:crypto';
function isFromStrawPoll(request, rawBody, secret) {
const id = request.headers['x-strawpoll-delivery'];
const timestamp = request.headers['x-strawpoll-timestamp'];
const signature = request.headers['x-strawpoll-signature'] ?? '';
const expected =
'sha256=' + createHmac('sha256', secret).update(`${id}.${timestamp}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return (
fresh &&
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
}Sign the body exactly as it arrived, before parsing it. Rejecting old timestamps stops someone from replaying a request they caught.
If your secret might have leaked, click Rotate on the integration’s page. The old secret stops working right away, so update your endpoint.
Respond in time
Answer with any 2xx status within 10 seconds. Do slow work after you’ve answered.
When a delivery fails, StrawPoll retries it three times over the next half minute, with the same id. It doesn’t retry a 4xx answer other than 429, and it never follows redirects. Every attempt shows up in Recent deliveries, with what your server answered.