Webhooks
Get notified in real-time via HTTPS when events happen in your SurveyThis account. Business plan required.
Overview
Webhooks let you receive HTTP POST requests to your server whenever specific events occur — a response is submitted, a survey is published, or an analysis completes. This enables real-time integrations with your CRM, Slack, analytics pipeline, or any other system.
Setting Up Webhooks
- Navigate to Dashboard → Settings.
- Find the Webhooks section (Business plan).
- Enter your endpoint URL (must be HTTPS).
- Select which events you want to receive.
- Optionally add a description.
- Click Create Webhook.
Important: When a webhook is created, a signing secret is displayed once. Copy it immediately — you'll need it to verify webhook signatures. It cannot be retrieved later.
Limits
- Up to 10 webhooks per account
- Each webhook can subscribe to multiple event types
- URLs must use HTTPS
Events
| Event | Fired When |
|---|---|
response.completed | A respondent submits a completed survey response |
survey.published | A survey status changes to "active" |
survey.closed | A survey status changes to "closed" |
analysis.completed | An AI analysis finishes processing |
You can also subscribe to * (wildcard) to receive all events.
Payload Format
Every webhook delivery is an HTTP POST request with a JSON body:
{
"event": "response.completed",
"timestamp": "2026-03-01T12:00:00.000Z",
"data": {
// Event-specific payload
}
}Event Payloads
response.completed
{
"event": "response.completed",
"timestamp": "2026-03-01T12:00:00.000Z",
"data": {
"survey_id": "550e8400-e29b-41d4-a716-446655440000",
"survey_title": "Customer Satisfaction Q1 2026",
"response_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"completed_at": "2026-03-01T12:00:00.000Z"
}
}survey.published
{
"event": "survey.published",
"timestamp": "2026-03-01T10:00:00.000Z",
"data": {
"survey_id": "550e8400-e29b-41d4-a716-446655440000",
"survey_title": "Customer Satisfaction Q1 2026",
"share_id": "aBcDeFgHiJ"
}
}survey.closed
{
"event": "survey.closed",
"timestamp": "2026-03-01T18:00:00.000Z",
"data": {
"survey_id": "550e8400-e29b-41d4-a716-446655440000",
"survey_title": "Customer Satisfaction Q1 2026",
"response_count": 347
}
}Verifying Signatures
Every webhook delivery includes an HMAC-SHA256 signature in the headers. Always verify this signature to ensure the request genuinely came from SurveyThis.
Headers Sent
| Header | Description |
|---|---|
X-SurveyThis-Signature | HMAC-SHA256 signature: sha256=<hex digest> |
X-SurveyThis-Event | Event name (e.g., response.completed) |
Content-Type | application/json |
User-Agent | SurveyThis-Webhooks/1.0 |
Verification Algorithm
- Read the raw request body (the JSON string)
- Compute HMAC-SHA256 of the body using your webhook signing secret as the key
- Compare with the value after
sha256=in theX-SurveyThis-Signatureheader - Use a constant-time comparison to prevent timing attacks
Node.js Example
const crypto = require('crypto');
function verifyWebhook(req, secret) {
const signature = req.headers['x-surveythis-signature'];
if (!signature) return false;
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
}
// Express example
app.post('/webhook', express.json(), (req, res) => {
if (!verifyWebhook(req, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
switch (event) {
case 'response.completed':
console.log('New response:', data.response_id);
// Sync to your CRM, send a Slack notification, etc.
break;
case 'survey.closed':
console.log('Survey closed with', data.response_count, 'responses');
break;
}
res.status(200).send('OK');
});Python Example
import hmac
import hashlib
import json
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-SurveyThis-Signature', '')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
payload = request.json
event = payload['event']
data = payload['data']
if event == 'response.completed':
print(f"New response: {data['response_id']}")
return 'OK', 200Failure Handling
- Timeout: Each delivery has a 10-second timeout. Your endpoint must respond within that window.
- Success: Any
2xxHTTP status is considered successful. On success, the failure counter resets to 0. - Failure: Non-2xx responses, timeouts, and connection errors increment the failure counter.
- Auto-disable: After 10 consecutive failures, the webhook is automatically disabled. You can re-enable it from the dashboard.
- No retries: Failed deliveries are not retried. However, every delivery attempt is logged in the delivery history for troubleshooting.
Testing
Use the Test button (lightning bolt icon) next to any webhook in the dashboard to send a test event:
{
"event": "test.ping",
"timestamp": "2026-03-01T12:00:00.000Z",
"data": {
"survey_id": "00000000-0000-0000-0000-000000000000",
"survey_title": "Test Survey",
"response_id": "00000000-0000-0000-0000-000000000000",
"test": true
}
}This helps you verify your endpoint is reachable, your signature verification works, and your handler processes events correctly — without waiting for a real event.
Delivery History
Every delivery attempt is logged with:
- Event name
- HTTP response status code
- Success/failure status
- Response time (milliseconds)
- Timestamp
View the last 50 deliveries for any webhook through the API:
GET /api/webhooks/:id/deliveries
Authorization: Bearer <session_token>Best Practices
- Respond quickly — Return a
200status immediately, then process the event asynchronously. The 10-second timeout is strict. - Verify signatures — Always validate the HMAC signature before processing events.
- Handle duplicates — While rare, your endpoint should be idempotent in case an event is received more than once.
- Use HTTPS — Webhook URLs must use HTTPS. Self-signed certificates are not supported.
- Monitor failures — Check the delivery history if your webhook stops receiving events. After 10 failures, it's auto-disabled.
- Use the test endpoint — Verify your setup before waiting for real events.