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

  1. Go to Account > Integrations > Webhooks
  2. Click Add Webhook
  3. Enter your endpoint URL
  4. Select events to subscribe to
  5. 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:

  1. Go to webhook settings
  2. Click Send Test
  3. Select event type
  4. 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:

  1. Go to Webhooks > Logs
  2. View attempts and responses
  3. Debug failed deliveries
  4. 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:

Was this article helpful?

Need more help?

Contact our support team for personalized assistance.

Contact Support →