Gareth Wilson Gareth Wilson

Guide to Survicate Webhooks: Features and Best Practices

Published


Survicate runs customer feedback surveys across channels. Its webhooks push responses out as they arrive, one per answer or one per completed survey, so you can route detractor scores to support, enrich a CRM contact or store raw feedback in your warehouse.

This guide covers Survicate's two events and their unusual key-based envelope, why to verify the Hmac header rather than the plain Token, and the delivery details Survicate doesn't publish.

What are Survicate webhooks?

Survicate webhooks are set up per workspace. The Webhooks Settings section of the Survicate panel holds the workspace's Verification Token and Signing Secret, which apply to every webhook Survicate sends from that workspace.

Survicate documents two events. questionAnswered fires for each answer to each question, so a five-question survey produces five requests. surveyCompleted fires once, and only when the respondent finishes the whole survey, with all the answers in one array.

Survicate webhook features

FeatureDetails
Signaturehex HMAC-SHA256
Signature headerhmac
BodyJSON

From Survicate's webhook documentation:

  • Notifications are sent as soon as a new submission comes in.
  • Each request carries two credentials: Token, the workspace Verification Token in plain text, and Hmac, a signature using the workspace Signing Secret. Survicate recommends the signature.
  • Every body includes messageId and deliveryTries, the number of attempts so far.

Events and payloads

EventMeaning
questionAnsweredQuestion answered event is triggered upon each respondent's answer. Each answer to every single question will be sent along with respondent data.
surveyCompletedSurvey answer event sends webhooks upon each survey completion. Please note that this event will be triggered only when the whole survey is completed.

The event name isn't a field. Every body is { messageId, deliveryTries, event: { <name>: { ... } } }, and the one key inside event tells you which event it is. Inside questionAnswered there's also a type field, but that's the question type, such as a choice or text question, not the event.

The questionAnswered example from Survicate's events page:

{
  "messageId": "c0101a21572ca643fd9f9ad40a5a4650",
  "deliveryTries": 0,
  "event": {
    "questionAnswered": {
      "responseUuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "survey": {
        "id": "b92b6ecae6dd7207",
        "name": "Satisfaction"
      },
      "url": "https://survey.survicate.com/43f2d5789346d6ad",
      "timestamp": "2019-05-10T06:46:02+00:00",
      "respondent": {
        "id": "534759",
        "uuid": "279cd4ab-8a07-4e9e-ac79-bcea32792bbc",
        "platform": "OSX",
        "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
        "attributes": [],
        "links": []
      },
      "question": {
        "id": 12123,
        "body": "How would you rate our customer service?"
      },
      "choice": {
        "id": 21573,
        "body": "Great",
        "comment": null
      }
    }
  }
}

surveyCompleted has the same responseUuid, survey, url, timestamp and respondent fields, plus a questionsAnswered array with one entry per answer. Answer fields depend on the question type: a choice question has choice with body and comment, while a text question has text. Survicate's question types page lists the rest. respondent.attributes holds any custom attributes you've passed to Survicate.

Setting up Survicate webhooks

  1. In the Survicate panel, open Webhooks Settings (under the workspace's integrations settings).
  2. Copy the Signing Secret and store it as SURVICATE_SIGNING_SECRET. The Verification Token on the same page is the plain-text alternative.
  3. Add your endpoint, for example https://example.com/webhooks/survicate, and choose questionAnswered, surveyCompleted or both.

To route through Hookdeck, create a Survicate source with the Signing Secret and use its URL as the endpoint. For local development:

npx hookdeck-cli listen 3000 survicate --path /webhooks/survicate

Securing Survicate webhooks

The Hmac header holds the lowercase hex HMAC-SHA256 of the request body, keyed with the Signing Secret. Survicate's security page notes that the hash must be in lowercase hex. The Token header is sent in plain text on every request, so anyone who sees one request has it. Survicate describes the HMAC as more secure, and it's what Hookdeck checks.

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['hmac'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const message = rawBody;
  const candidates = [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);
  });
}

The handler finds the event by its key, checks the inner object, and stores the response by messageId.

const express = require('express');

const app = express();

app.post('/webhooks/survicate', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.SURVICATE_SIGNING_SECRET;
  if (!secret) return res.sendStatus(500);
  if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);

  let body;
  try {
    body = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!body || typeof body !== 'object' || Array.isArray(body)) return res.sendStatus(400);
  if (!body.event || typeof body.event !== 'object') return res.sendStatus(400);

  const [eventName] = Object.keys(body.event);
  const detail = body.event[eventName];
  if (!detail || typeof detail !== 'object') return res.sendStatus(400);

  try {
    if (await seenMessage(body.messageId)) return res.sendStatus(200);
    await saveResponse(eventName, {
      responseUuid: detail.responseUuid,
      surveyId: detail.survey?.id,
      respondentId: detail.respondent?.id,
      answers: Array.isArray(detail.questionsAnswered) ? detail.questionsAnswered : [detail],
    });
  } catch {
    return res.sendStatus(503);
  }
  res.sendStatus(200);
});

Hookdeck's Survicate source checks the Hmac header and ignores Token. It responds 200 and records a mismatch as failed verification. With no timestamp in Survicate's signature, Hookdeck applies no age limit either.

Make Survicate webhooks production-ready. Hookdeck Event Gateway keeps every answer Survicate sends, so feedback isn't lost while your CRM or warehouse sync is down.

Survicate webhook limitations and pain points

The event type is a key name

The Problem: Generic routers that look for a type or event field can't route Survicate requests, and questionAnswered.type looks like an event type but isn't.

Why It Happens: Survicate nests the payload under a key named after the event, inside event.

Workarounds:

  • Read the single key of body.event and treat that as the event name.
  • Ignore type inside the payload for routing.

How Hookdeck Can Help: Hookdeck filters can match on the presence of event.questionAnswered or event.surveyCompleted and route each to its own destination.

Delivery behaviour is undocumented

The Problem: You don't know when Survicate retries or for how long.

Why It Happens: Bodies include deliveryTries, which shows retries exist, but no schedule, timeout or success status is documented.

Workarounds:

  • Return 200 quickly once the response is stored.
  • Use deliveryTries to spot requests that needed several attempts.

How Hookdeck Can Help: Hookdeck accepts each request at once and retries your service on a schedule you set.

Per-answer events multiply quickly

The Problem: A busy survey with many questions generates a request for every answer.

Why It Happens: questionAnswered fires for each answer to each question.

Workarounds:

  • Use surveyCompleted if you only need full responses.
  • Group per-answer events by responseUuid in your store.

How Hookdeck Can Help: Hookdeck absorbs bursts and can rate-limit delivery to your service.

Partial responses don't produce surveyCompleted

The Problem: Respondents who stop halfway never appear in surveyCompleted data.

Why It Happens: Survicate sends surveyCompleted only when the whole survey is completed. Whether other partial-response events exist isn't documented.

Workarounds:

  • Subscribe to questionAnswered as well if partial answers matter.
  • Reconcile counts against Survicate's own reports.

How Hookdeck Can Help: Routing both events through Hookdeck lets you keep per-answer data in one place and full responses in another.

Best practices

Verify the Hmac, not the Token

The plain Token proves little once it's been logged anywhere. Use the HMAC, as Survicate recommends.

Deduplicate on messageId

Survicate describes messageId as the webhook ID. Store it before writing the response, so a resend of the same message is skipped, as described in our guide to idempotent webhook handling.

Handle each question type

Write a small adapter per question type you use (choice, text and so on) instead of assuming choice is always present.

Write later, respond now

Queue the response and acknowledge it, then sync to your CRM. Our article on processing webhooks asynchronously explains why.

Conclusion

Survicate sends per-answer and per-completion webhooks with the event name as a key inside event, signed with a lowercase hex HMAC-SHA256 in Hmac. Verify the HMAC rather than the plain token, route on the key name, and deduplicate on messageId, since the retry policy isn't published.

Hookdeck Event Gateway verifies Survicate requests, routes the two events and retries delivery to your systems.

Get started with Hookdeck for free and handle Survicate webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.