Webhooks
Webhooks allow your application to receive real-time notifications when events occur in SurveyMethods.
Overview
What Are Webhooks?
Webhooks are HTTP callbacks that send data to your specified URL when events happen:
- Survey response submitted
- Survey status changed
- Contact opted out
- Email bounced
Benefits
- Real-time updates - Instant notifications
- No polling required - Reduce API calls
- Automated workflows - Trigger actions immediately
- Event-driven architecture - Modern integration pattern
Setting Up Webhooks
Creating a Webhook
- Go to Account > Integrations > Webhooks
- Click Add Webhook
- Enter your endpoint URL
- Select events to subscribe to
- Save and test
Webhook Configuration
Endpoint URL:
https://your-app.com/webhooks/surveymethods
Requirements:
- Must be HTTPS
- Must return 2xx status
- Should respond within 30 seconds
Secret Key:
- Auto-generated for signature verification
- Used to validate webhook authenticity
- Store securely in your application
Available Events
Response Events
response.completed
- Triggered when survey submitted
- Includes full response data
response.started
- Triggered when survey started
- Includes partial response ID
response.updated
- Triggered for save-and-continue
- Includes updated response data
Survey Events
survey.activated
- Survey set to active status
survey.closed
- Survey collection closed
survey.published
- Survey published/updated
Collector Events
collector.created
- New collector created
collector.closed
- Collector closed
Email Events
email.sent
- Invitation/reminder sent
email.bounced
- Email delivery failed
email.opened
- Email opened by recipient
email.clicked
- Survey link clicked
contact.unsubscribed
- Contact opted out
Webhook Payload
Payload Structure
All webhooks send JSON:
{
"event": "response.completed",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "wh_abc123",
"data": {
"surveyId": "srv_xyz789",
"responseId": "rsp_def456",
"collectorId": "col_ghi012",
"completedAt": "2024-01-15T10:30:00Z",
"answers": [
{
"questionId": "q1",
"questionText": "How satisfied are you?",
"value": 5,
"type": "rating"
}
]
}
}
Response Completed Example
{
"event": "response.completed",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"surveyId": "srv_xyz789",
"surveyTitle": "Customer Satisfaction",
"responseId": "rsp_def456",
"collectorId": "col_ghi012",
"collectorType": "email",
"respondent": {
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
},
"completedAt": "2024-01-15T10:30:00Z",
"duration": 245,
"answers": [
{
"questionId": "q1",
"questionText": "Overall satisfaction",
"type": "nps",
"value": 9
},
{
"questionId": "q2",
"questionText": "Comments",
"type": "text",
"value": "Great service!"
}
],
"metadata": {
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"customVariables": {
"source": "email_campaign_q1"
}
}
}
}
Verifying Webhooks
Signature Verification
Each webhook includes a signature header:
X-SurveyMethods-Signature: sha256=abc123...
Verify to ensure authenticity:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return `sha256=${expected}` === signature;
}
// In your webhook handler
app.post('/webhooks/surveymethods', (req, res) => {
const signature = req.headers['x-surveymethods-signature'];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook
processWebhook(req.body);
res.status(200).send('OK');
});
Timestamp Validation
Prevent replay attacks by checking timestamp:
const timestamp = new Date(payload.timestamp);
const now = new Date();
const fiveMinutes = 5 * 60 * 1000;
if (now - timestamp > fiveMinutes) {
// Reject old webhooks
return res.status(400).send('Webhook too old');
}
Handling Webhooks
Best Practices
Respond quickly:
- Return 2xx immediately
- Process asynchronously
app.post('/webhooks', async (req, res) => {
// Acknowledge immediately
res.status(200).send('OK');
// Process in background
processWebhookAsync(req.body);
});
Handle duplicates:
- Webhooks may be sent multiple times
- Use webhookId for idempotency
async function processWebhook(payload) {
// Check if already processed
const existing = await db.webhooks.find(payload.webhookId);
if (existing) return;
// Process and mark as handled
await handleEvent(payload);
await db.webhooks.insert(payload.webhookId);
}
Implement retries:
- Your endpoint may be temporarily unavailable
- Handle gracefully when back online
Retry Policy
Automatic Retries
Failed webhooks are retried:
- Attempt 1: Immediate
- Attempt 2: 1 minute
- Attempt 3: 5 minutes
- Attempt 4: 30 minutes
- Attempt 5: 2 hours
- Attempt 6: 24 hours
Failure Conditions
Webhooks are retried when:
- Non-2xx response
- Timeout (30 seconds)
- Connection error
After all retries fail:
- Webhook marked as failed
- Alert sent to account
- Available in webhook logs
Testing Webhooks
Test Endpoint
Send test webhooks:
- Go to webhook settings
- Click Send Test
- Select event type
- Review delivery result
Development Tools
Use webhook testing services:
- webhook.site
- requestbin.com
- ngrok for local development
Local testing with ngrok:
ngrok http 3000
# Use the ngrok URL as your webhook endpoint
View Webhook Logs
Monitor all webhook activity:
- Go to Webhooks > Logs
- View attempts and responses
- Debug failed deliveries
- Resend failed webhooks
Common Use Cases
Sync to CRM
Update CRM when response received:
app.post('/webhooks', async (req, res) => {
if (req.body.event === 'response.completed') {
const { respondent, answers } = req.body.data;
await crm.updateContact(respondent.email, {
surveyResponses: answers,
lastSurveyDate: new Date()
});
}
res.status(200).send('OK');
});
Trigger Notifications
Alert team on low scores:
if (req.body.event === 'response.completed') {
const npsAnswer = answers.find(a => a.type === 'nps');
if (npsAnswer && npsAnswer.value <= 6) {
await slack.send({
channel: '#customer-alerts',
text: `Low NPS score (${npsAnswer.value}) from ${respondent.email}`
});
}
}
Build Analytics Pipeline
Stream responses to data warehouse:
if (req.body.event === 'response.completed') {
await dataWarehouse.insert('survey_responses', {
responseId: data.responseId,
surveyId: data.surveyId,
completedAt: data.completedAt,
answers: JSON.stringify(data.answers)
});
}
Troubleshooting
Webhooks Not Receiving
- Verify endpoint URL is correct
- Check endpoint is publicly accessible
- Ensure HTTPS certificate is valid
- Test with webhook.site first
Authentication Failures
- Verify secret key is correct
- Check signature calculation
- Ensure payload isn’t modified
Missed Events
- Check webhook event subscriptions
- Review webhook logs for failures
- Verify endpoint uptime
Related articles: