Webhooks
Webhooks let ResponseDock push real-time survey events to your systems — CRM, data warehouse, notification service, or custom backend.
Creating a Webhook
- Go to Workspace → Webhooks
- Click + Add Endpoint
- Enter the HTTPS URL that will receive events
- Select which events to subscribe to
- Save — ResponseDock generates a signing secret
Supported Events
| Event | When it fires |
|---|---|
survey.response.started | A respondent starts a survey response |
survey.response.completed | A respondent completes and submits a survey |
survey.response.hidden | An admin hides a response from analytics and exports |
survey.response.restored | An admin restores a hidden response |
survey.response.deleted | An admin permanently deletes a response |
survey.responses.generated | An admin generates sample responses |
survey.attachment.deleted | An admin permanently deletes a file uploaded with a response |
survey.created | A survey is created |
survey.updated | Survey metadata is changed |
survey.published | A draft survey version is published |
survey.paused | A live survey is paused |
survey.closed | A survey is closed |
survey.archived | A survey is archived |
survey.deleted | A survey is permanently deleted |
template.created | A survey template is created |
template.deleted | A survey template is deleted |
workspace.member.invited | Someone is invited to the workspace |
workspace.member.joined | An invited person joins the workspace |
workspace.member.role_changed | A member's role changes |
workspace.member.removed | A member is removed from the workspace |
workspace.invitation.revoked | A pending invitation is revoked |
api_key.created | An API key is created (the key itself is never sent) |
api_key.rotated | An API key is rotated |
api_key.revoked | An API key is revoked |
trigger.created | A targeting trigger is created |
trigger.updated | A targeting trigger is changed |
trigger.deleted | A targeting trigger is deleted |
insight.created | An insight is recorded manually or from an AI draft |
insight.updated | An insight is changed |
Payload Structure
{
"event": "survey.response.completed",
"data": {
"workspaceId": "ws_xxxx",
"surveyId": "survey_yyyy",
"surveySlug": "customer-feedback",
"responseId": "resp_aaaa",
"completedAt": "2024-11-14T10:30:00Z",
"isAnonymous": false,
"answers": { "count": 1 },
"source": {
"channel": "sdk",
"campaignId": null,
"utmSource": "email",
"referrer": null
},
"followUpEmail": "[redacted]",
"quiz": { "score": 8, "maxScore": 10, "scorePercent": 80, "passed": true, "pendingReview": 0 }
}
}Webhook payloads carry only the number of answers, never the answers themselves, and email addresses and tokens are redacted. For quizzes, identified responses also carry a quiz score summary (passed is null without a passing score or while answers await review); it never includes the answer key, and anonymous responses get no score. Fetch the response through the Responses API when you need the answers.
Signature Verification
Every request includes X-ResponseDock-Event, X-ResponseDock-Timestamp, and X-ResponseDock-Signature headers. Verify the signature to confirm the request comes from ResponseDock:
import crypto from 'crypto';
function verifySignature(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature.replace('sha256=', ''))
);
}Prefer X-ResponseDock-Signature-V2, which also covers the timestamp so you can reject replayed requests: it is sha256= followed by the hex HMAC-SHA256 of `${timestamp}.${body}`, keyed with the signing secret, where timestamp is the X-ResponseDock-Timestamp header (milliseconds since the epoch).
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignatureV2(rawBody: string, headers: Record<string, string>, secret: string): boolean {
const timestamp = headers['x-responsedock-timestamp'];
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60_000) return false; // too old
const expected = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const received = headers['x-responsedock-signature-v2'] ?? '';
return received.length === expected.length && timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}Retry Policy
If your endpoint returns a non-2xx status, ResponseDock retries with exponential back-off:
| Attempt | Delay |
|---|---|
| 1st retry | 5 minutes |
| 2nd retry | 15 minutes |
| 3rd retry | 1 hour |
| 4th retry | 3 hours |
| 5th retry | 6 hours |
| After 5th failure | Delivery marked failed; no further retries |
Delivery Logs
Every delivery attempt is logged. View logs in Workspace → Webhooks → [endpoint] → Delivery Log to see the request body, response status, and next retry time.
Testing Webhooks Locally
# Use ngrok or a similar tunnel ngrok http 3000 # Use the public URL as your webhook endpoint in the dashboard # https://abc123.ngrok.io/webhooks/responsedock