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

  1. Open Integrations in your dashboard, click Add an integration and choose Webhooks.
  2. Enter your endpoint’s URL. It has to start with https:// and be reachable from the internet.
  3. Optional: add headers your endpoint expects, for example an Authorization header.
  4. Choose the poll and whether to send new responses, changed responses or both, then save.
  5. Copy the Signing secret from the integration’s page, then click Send test.

The payload

json
{
	"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
	}
}
  • event is response.created, response.updated, response.deleted or ping for Send test.
  • For response.deleted, deletedAt says when the respondent deleted their response and answers holds what they had sent. Remove it from your own records too.
  • id is unique for every delivery. Use it to ignore a delivery you’ve already handled.
  • answers lists every question, in order. text is the answer as it reads in your results; value is the raw value, whose shape depends on the question type.

Headers

HeaderWhat’s in it
X-StrawPoll-EventThe event, like response.created
X-StrawPoll-DeliveryThe delivery’s id
X-StrawPoll-TimestampWhen it was sent, in Unix seconds
X-StrawPoll-Signaturesha256= 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:

js
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.