Skip to content

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

  1. Navigate to Dashboard → Settings.
  2. Find the Webhooks section (Business plan).
  3. Enter your endpoint URL (must be HTTPS).
  4. Select which events you want to receive.
  5. Optionally add a description.
  6. 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

EventFired When
response.completedA respondent submits a completed survey response
survey.publishedA survey status changes to "active"
survey.closedA survey status changes to "closed"
analysis.completedAn 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

HeaderDescription
X-SurveyThis-SignatureHMAC-SHA256 signature: sha256=<hex digest>
X-SurveyThis-EventEvent name (e.g., response.completed)
Content-Typeapplication/json
User-AgentSurveyThis-Webhooks/1.0

Verification Algorithm

  1. Read the raw request body (the JSON string)
  2. Compute HMAC-SHA256 of the body using your webhook signing secret as the key
  3. Compare with the value after sha256= in the X-SurveyThis-Signature header
  4. 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', 200

Failure Handling

  • Timeout: Each delivery has a 10-second timeout. Your endpoint must respond within that window.
  • Success: Any 2xx HTTP 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 200 status 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.