Guide to SurveyMethods Webhooks: Features and Best Practices
SurveyMethods is an online survey platform for building surveys, distributing them through collectors such as web links, email invitations and embeds, and analysing the answers. Its webhooks send a signed JSON event when a respondent opens or finishes a survey, or when someone in the account creates a survey or collector. The usual use is pushing completed responses into a CRM, help desk or warehouse as they arrive.
This guide covers creating SurveyMethods webhooks in the dashboard or through the API, the four event types and their payload, verifying the sha256= signature header, and how the 30-second, five-attempt delivery model affects your receiver.
What are SurveyMethods webhooks?
A SurveyMethods webhook is a public HTTPS URL, a set of event types and a scope, which is either every survey in the account or one survey. Each webhook gets its own signing secret. An account can have up to 25 active webhooks, available on the Professional, Team Professional and Enterprise plans.
Deliveries are POST requests with a JSON body. SurveyMethods won't send to private-network addresses and doesn't follow redirects, so the URL you register must be the final public endpoint. Delivery is at-least-once: a 2xx within 30 seconds acknowledges the event, and anything else leads to more attempts. Every webhook has a delivery log under Account > Webhooks.
SurveyMethods webhook features
| Feature | Details |
|---|---|
| Signature | hex HMAC-SHA256 |
| Signature header | x-surveymethods-signature |
| Body | JSON |
| Event routing | Body field eventType |
Events and payloads
| Event | Triggered When | Common Use Cases |
|---|---|---|
response.completed | A respondent completes a survey response | Sync answers to a CRM or warehouse, alert a team |
response.created | A survey response is started | Track response starts and drop-off |
survey.created | A survey is created | Catalogue surveys, provision dashboards |
collector.created | A collector is created | Track distribution channels |
response.created fires when a respondent opens the survey, before any answer, so comparing it with response.completed gives you a drop-off rate per survey or collector.
Event Payload Structure
Every event has the same envelope: eventId, eventType, eventTime and a type-specific data object. Two headers repeat envelope fields, X-SurveyMethods-Event-Type and X-SurveyMethods-Event-Id, so you can route or deduplicate before parsing. The User-Agent is SurveyMethods-Webhooks/1.0.
For response.completed, data identifies the survey, respondent and collector, gives the completion time, and holds responses: a map from question ID to the stored answer. An answer can be a choice ID, free text or JSON, depending on the question type, so you need the survey's question definitions to turn IDs into readable answers.
The other three events are documented by field list only, with no example body. Capture a real delivery before writing a parser for them.
Published payload example
This is the response.completed sample from the webhook samples library, based on SurveyMethods' documentation:
{
"eventId": "6a1f0c3e-2b9d-4f7a-9c51-0e8d2f4b7a10",
"eventType": "response.completed",
"eventTime": "2026-10-23T10:15:02Z",
"data": {
"surveyId": "12345",
"respondentId": 6789,
"collectorId": 456,
"completedDateTimeUtc": "2026-10-23T10:15:00Z",
"responses": {
"910001": "3",
"910002": "Great support team."
}
}
}
Setting up SurveyMethods webhooks
Prerequisites
- A SurveyMethods account on the Professional, Team Professional or Enterprise plan
- A public HTTPS endpoint that answers directly, without redirects
Register Your Endpoint
- Open Account > Webhooks and click Create Webhook, or
POSTto/api/v1/webhooks. - Enter the endpoint URL.
- Pick the event types, and choose all surveys or a single survey.
- Save.
Get Your Signing Secret
The secret appears once, straight after saving, or once in the response to the create API call. Copy it into WEBHOOK_SECRET at that moment. Each webhook has its own secret, so a receiver serving several webhooks needs to know which secret applies to which URL.
Test a delivery
Start a local receiver and forward requests with the Hookdeck CLI:
npx hookdeck-cli listen 3000 surveymethods --path /webhooks/surveymethods
Create a webhook for a test survey pointing at the CLI URL with all four events selected. Create a collector, open the survey link, then submit it. You'll get collector.created, response.created and response.completed in turn, which gives you real bodies for the events SurveyMethods documents without examples.
Securing SurveyMethods webhooks
X-SurveyMethods-Signature holds sha256= followed by the lowercase hex HMAC-SHA256 of the raw body, keyed with the webhook's secret as UTF-8. Strip the prefix, compute the digest over the exact bytes received, and compare in constant time. SurveyMethods' Node, Python and C# samples all follow this. No timestamp is signed, so the signature can't tell you whether a request is fresh. See the webhooks article.
Node.js verification
const crypto = require('node:crypto');
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const signature = headers['x-surveymethods-signature'];
if (typeof signature !== 'string') return false;
const key = Buffer.from(secret, 'utf8');
const message = rawBody;
const candidates = [signature.startsWith('sha256=') ? signature.slice(7) : signature];
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
const received = Buffer.from(candidate, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
After verification, the handler deduplicates on eventId and routes on eventType. A processing error returns 503, which SurveyMethods will retry within its five attempts.
const express = require('express');
function createSurveyMethodsApp({ isProcessed, markProcessed, saveResponse, recordStart, recordSurvey, recordCollector }) {
const app = express();
app.post('/webhooks/surveymethods', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
let parsed;
try {
parsed = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
const { eventId, eventType, eventTime, data } = parsed;
if (!eventId || typeof eventType !== 'string' || !data || typeof data !== 'object') {
return res.sendStatus(400);
}
try {
if (await isProcessed(eventId)) return res.sendStatus(200);
switch (eventType) {
case 'response.completed':
await saveResponse(data.surveyId, data.respondentId, data.responses, data.completedDateTimeUtc);
break;
case 'response.created':
await recordStart(data, eventTime);
break;
case 'survey.created':
await recordSurvey(data);
break;
case 'collector.created':
await recordCollector(data);
break;
default:
console.log('Unhandled SurveyMethods event', eventType);
}
await markProcessed(eventId);
} catch {
return res.sendStatus(503);
}
return res.sendStatus(200);
});
return app;
}
Use eventTime for reporting, not for a freshness check: it isn't covered by the signature.
When the webhook points at a Hookdeck SurveyMethods source, Hookdeck reads the sha256= value and checks it against an HMAC of the body. SurveyMethods always gets a 200 back. Requests that don't match are logged as failed verification and stop there, and no timestamp window is applied.
Turn survey responses into reliable events. Hookdeck Event Gateway verifies each SurveyMethods delivery, acknowledges inside the 30-second limit and gives you retries and replay beyond SurveyMethods' five attempts.
SurveyMethods webhook limitations and pain points
Five attempts, unknown spacing
The Problem: A longer outage can use up all of SurveyMethods' attempts.
Why It Happens: SurveyMethods tries up to 5 times in total, "spaced further apart each time", but doesn't give the intervals.
Workarounds:
- Check the delivery log under Account > Webhooks after any outage.
- Pull missed responses through SurveyMethods' API or exports.
How Hookdeck Can Help: Hookdeck accepts the event on the first attempt and keeps retrying your endpoint on its own schedule, with manual replay after that.
Secrets are shown once
The Problem: Losing the secret leaves you unable to verify that webhook.
Why It Happens: The secret is displayed once after saving, or returned once by the API. The docs don't describe a way to view or rotate it later.
Workarounds:
- Write the secret to your secrets manager as part of creating the webhook.
- If it's lost, create a replacement webhook and delete the old one.
How Hookdeck Can Help: Store the secret on the Hookdeck source once, and your services downstream don't need it at all.
Most payloads have no example
The Problem: Parsers for response.created, survey.created and collector.created are written blind.
Why It Happens: SurveyMethods documents those events as field lists without example bodies.
Workarounds:
- Generate each event in a test survey and save the bodies as fixtures.
- Validate fields before use and log unexpected shapes.
How Hookdeck Can Help: Every delivery's body and headers are kept in Hookdeck, so real examples are available as soon as the events fire.
Strict URL rules
The Problem: A URL behind a redirect or on a private network never receives events.
Why It Happens: SurveyMethods doesn't follow redirects and refuses private-network addresses.
Workarounds:
- Register the final HTTPS URL, and update webhooks when it moves.
How Hookdeck Can Help: The Hookdeck source URL stays the same while you move or rename the services behind it.
25 webhooks per account
The Problem: Per-survey webhooks run out quickly in large accounts.
Why It Happens: An account can have at most 25 active webhooks, each scoped to one survey or all of them.
Workarounds:
- Use one all-surveys webhook and route on
data.surveyIdin your receiver.
How Hookdeck Can Help: Hookdeck filters on body fields, so a single webhook can be split into per-survey connections.
Best practices
Map question IDs once
Fetch each survey's question definitions and cache them, so responses can be stored with readable labels. Refresh the cache when a survey is edited, because survey.created only fires once.
Deduplicate from the header
X-SurveyMethods-Event-Id lets you skip a repeat before parsing the body. Keep processed IDs for longer than the retry window.
Respond inside 30 seconds
Store the event and return 200, then do enrichment and CRM calls from a queue.
Treat answers as personal data
Free-text answers can contain anything. Keep responses out of application logs and apply the same retention rules as the rest of your survey data.
Conclusion
SurveyMethods' webhooks are well specified where it matters most: a stable eventId, event headers, a per-webhook secret and a clear 30-second acknowledgement rule. The gaps are smaller but real: an unstated retry schedule, one-time secrets, example bodies for only one event, and strict URL rules.
Hookdeck Event Gateway adds longer retries, replay and a stable endpoint in front of your survey integrations.
Get started with Hookdeck for free and handle SurveyMethods webhooks reliably in minutes.