Webhook Events Reference
This page documents all webhook events, their triggers, and payload structures.
Event Flow
Common Payload Structure
All webhook payloads share a common structure:
Base payload structure
{
"event": "EVENT_TYPE",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "wh_abc123",
"departmentId": "dept_xyz789",
"data": { /* Event-specific data */ }
}
Common Fields
| Field | Type | Description |
|---|---|---|
event | string | The event type that triggered this webhook |
timestamp | string | ISO 8601 timestamp of when the event occurred |
webhookId | string | Unique identifier for the webhook configuration |
departmentId | string | The department where the event occurred |
data | object | Event-specific payload data |
SURVEY_COMPLETED
Triggered when a respondent submits a completed survey response.
When It Fires
- Respondent clicks submit on the final page
- All required questions have been answered
- Survey response is successfully saved
📦 Full Payload Example (click to expand)
SURVEY_COMPLETED payload
{
"event": "SURVEY_COMPLETED",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "wh_abc123",
"departmentId": "dept_xyz789",
"data": {
"surveyId": "survey_123",
"surveyName": "Customer Satisfaction Survey",
"responseId": "resp_456",
"collectorId": "col_789",
"collectorType": "email",
"completedAt": "2024-01-15T10:30:00Z",
"contact": {
"id": "contact_001",
"email": "customer@example.com",
"phone": "+1234567890",
"customFields": {
"firstName": "John",
"lastName": "Doe"
}
},
"responses": [
{
"questionId": "q1",
"questionTitle": "How satisfied are you?",
"questionType": "rating",
"answer": 9
},
{
"questionId": "q2",
"questionTitle": "Would you recommend us?",
"questionType": "boolean",
"answer": true
},
{
"questionId": "q3",
"questionTitle": "Additional comments",
"questionType": "text",
"answer": "Great service!"
}
],
"metadata": {
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"duration": 245,
"language": "en"
}
}
}
Data Fields
| Field | Type | Description |
|---|---|---|
surveyId | string | Unique survey identifier |
surveyName | string | Name of the survey |
responseId | string | Unique response identifier |
collectorId | string | Collector that gathered this response |
collectorType | string | Type: email, sms, whatsapp, web, widget |
completedAt | string | ISO 8601 completion timestamp |
contact | object | Respondent contact information (if available) |
responses | array | Array of question-answer pairs |
metadata | object | Additional response metadata |
Handler Examples
- Node.js
- Python
- cURL (Test)
server.js
const express = require('express');
const app = express();
app.post('/webhooks/pollarix', express.json(), (req, res) => {
const { event, data } = req.body;
if (event === 'SURVEY_COMPLETED') {
// Extract NPS score
const npsQuestion = data.responses.find(r => r.questionType === 'rating');
const score = npsQuestion?.answer;
console.log(`New response! Score: ${score}, Email: ${data.contact.email}`);
// Sync to your CRM
syncToCRM(data.contact.email, score);
}
res.status(200).send('OK');
});
app.listen(3000);
app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhooks/pollarix', methods=['POST'])
def handle_webhook():
payload = request.json
event = payload.get('event')
data = payload.get('data')
if event == 'SURVEY_COMPLETED':
# Extract NPS score
nps_response = next(
(r for r in data['responses'] if r['questionType'] == 'rating'),
None
)
score = nps_response['answer'] if nps_response else None
print(f"New response! Score: {score}, Email: {data['contact']['email']}")
# Sync to your CRM
sync_to_crm(data['contact']['email'], score)
return 'OK', 200
Test webhook locally
curl -X POST http://localhost:3000/webhooks/pollarix \
-H "Content-Type: application/json" \
-d '{
"event": "SURVEY_COMPLETED",
"timestamp": "2024-01-15T10:30:00Z",
"webhookId": "wh_test",
"departmentId": "dept_test",
"data": {
"surveyId": "survey_123",
"responseId": "resp_456",
"contact": {
"email": "test@example.com"
},
"responses": [
{
"questionId": "q1",
"questionType": "rating",
"answer": 9
}
]
}
}'
SURVEY_STATUS_CHANGED
Triggered when a survey's status is modified.
When It Fires
- Survey is activated (draft → active)
- Survey is deactivated (active → inactive)
- Survey is closed
- Survey is reopened
📦 Full Payload Example
SURVEY_STATUS_CHANGED payload
{
"event": "SURVEY_STATUS_CHANGED",
"timestamp": "2024-01-15T14:00:00Z",
"webhookId": "wh_abc123",
"departmentId": "dept_xyz789",
"data": {
"surveyId": "survey_123",
"surveyName": "Customer Satisfaction Survey",
"previousStatus": "draft",
"newStatus": "active",
"changedBy": {
"userId": "user_456",
"email": "admin@company.com"
},
"changedAt": "2024-01-15T14:00:00Z"
}
}
Status Values
| Status | Description |
|---|---|
draft | Survey is being edited, not accepting responses |
active | Survey is live and accepting responses |
inactive | Survey is paused, not accepting responses |
closed | Survey is permanently closed |
Handler Examples
- Node.js
- Python
Notify on status change
if (event === 'SURVEY_STATUS_CHANGED' && data.newStatus === 'active') {
await slack.postMessage({
channel: '#surveys',
text: `Survey "${data.surveyName}" is now live!`
});
}
// Archive data when survey closes
if (data.newStatus === 'closed') {
await archiveSurveyData(data.surveyId);
}
Notify on status change
if event == 'SURVEY_STATUS_CHANGED' and data['newStatus'] == 'active':
slack.post_message(
channel='#surveys',
text=f'Survey "{data["surveyName"]}" is now live!'
)
# Archive data when survey closes
if data['newStatus'] == 'closed':
archive_survey_data(data['surveyId'])
COUPON_REDEEMED
Triggered when a coupon is distributed to a survey respondent.
When It Fires
- Automation workflow distributes a coupon
- Coupon is successfully assigned to a contact
- Coupon delivery method is executed (email, SMS, or WhatsApp)
📦 Full Payload Example
COUPON_REDEEMED payload
{
"event": "COUPON_REDEEMED",
"timestamp": "2024-01-15T10:35:00Z",
"webhookId": "wh_abc123",
"departmentId": "dept_xyz789",
"data": {
"couponId": "coupon_123",
"couponCode": "SAVE20NOW",
"couponGroupId": "group_456",
"couponGroupName": "January Promotions",
"responseId": "resp_789",
"surveyId": "survey_123",
"contact": {
"id": "contact_001",
"email": "customer@example.com",
"phone": "+1234567890"
},
"deliveryMethod": "email",
"deliveredAt": "2024-01-15T10:35:00Z",
"expiresAt": "2024-02-15T23:59:59Z"
}
}
Data Fields
| Field | Type | Description |
|---|---|---|
couponId | string | Unique coupon identifier |
couponCode | string | The actual coupon code |
couponGroupId | string | Coupon group identifier |
couponGroupName | string | Name of the coupon group |
responseId | string | Survey response that triggered the coupon |
surveyId | string | Associated survey |
contact | object | Recipient contact information |
deliveryMethod | string | How coupon was sent: email, sms, whatsapp |
deliveredAt | string | ISO 8601 delivery timestamp |
expiresAt | string | Coupon expiration date |
Response Handling
Important
Always respond quickly (< 5 seconds) to webhook requests. Process data asynchronously.
| Response Code | Meaning | Pollarix Action |
|---|---|---|
2xx | ✅ Success | Delivery confirmed |
4xx | ❌ Client error | Will not retry |
5xx | ⚠️ Server error | May retry |
| Timeout | ⏱️ No response | May retry |
Idempotent Handler Pattern
Idempotent webhook handler
const processedEvents = new Set();
app.post('/webhooks/pollarix', (req, res) => {
// Create unique key to prevent duplicate processing
const eventKey = `${req.body.webhookId}-${req.body.timestamp}`;
if (processedEvents.has(eventKey)) {
return res.status(200).send('Already processed');
}
processedEvents.add(eventKey);
// Process webhook async
processWebhookAsync(req.body);
res.status(200).send('OK');
});
Next Steps
- API Reference - Manage webhooks programmatically
- Security Best Practices - Secure your webhook endpoints
Was this page helpful?