Skip to main content

Webhook v2.0.0

On visitor action (such as clicking on a choice, scoring, input...) Screeb can send a request to an external platform, containing the response data.

Entities

  • Organization: A Screeb account, shared by many Screeb users of your company
  • Channel: A distribution channel for your survey (Javascript SDK, Android SDK, iOS SDK...)
  • Survey
  • Survey scenario: A versioned survey: on survey update, your flow is saved in a new scenario. New users will see the last scenario.
  • User: Information about the visitor
  • Response: A response is a chain of question+answers. A user sometimes has many responses to a single survey.
  • Question
  • Answer: An answer is part of a response. Each answer is associated with a question.
  • Field: An answer may have many fields (eg: multiple choices question)

Hook types

You can trigger webhooks on 3 different events:

  • On survey display: a survey is shown to visitors, but no question is replied. You will get notified even if no response is given.
  • On question answered: a question has been replied. This webhook will be triggered for each question of a single survey.
  • On response end: a survey has been closed or fully replied to by the user.

Errors

On large synchronization order, the webhook destination may reply to Screeb with a 429 HTTP error (rate limiting). Screeb will resend these messages many times, with exponential delay.

Migrate from v1.3.0

  • Each correlation_id has been renamed id, and the previous id property is not available anymore.
  • payload.respondent has been renamed payload.user
  • payload.response.answer.field has been converted into a array: payload.response.answer.fields

Example payload

Webhooks deliver the responses to your surveys in JSON format, via a POST HTTP request. Here is a typical webhook payload:

{
"event_id": "64c7ea3b-827b-4679-b25d-7fd61f6c3d33",
"event_type": "response.ended",
"version": "2.0.0",
"time": "2021-07-29T13:45:09.325344042Z",
"time_ms": 1627566309325,
"payload": {
"organization": {
"id": "b5969d13-5e5e-4648-9806-5339ddafd984",
"name": "ACME"
},
"channel": {
"id": "411ee7c3-7e64-409f-9d75-c782b3f73aa4",
"type": "android"
},
"survey": {
"id": "9b913c69-3daf-4a6e-a26d-042004fc7881",
"name": "Measure NPS",
"scenario_id": "cc4a3710-e8ee-4672-a35c-5c17485ec441",
"scenario_version": 9,
"scenario_time": "2021-07-29T00:08:31.069497Z",
"scenario_time_ms": 1627517311069
},
"user": {
"anonymous_id": "2eb83fb4-b1b3-4e48-be48-a8fd9c4e5a7d",
"user_id": "[email protected]",
"name": "Samuel Berthe",
"email": "[email protected]",
"group_names": ["Screeb", "10-100-companies", "plan-enterprise"]
},
"response": {
"id": "5854a797-628c-4906-bb4c-da03e418cf47",
"locale": "en-US",
"time": "2021-07-29T13:44:59.831Z",
"time_ms": 1627918228831,
"time_to_complete_second": 34,
"completion": "fully_completed",
"hidden_fields": {
"firstname": "Samuel",
"lastname": "Berthe",
"email": "[email protected]",
"locale": "en-US",
"support": "desktop",
"timezone": -120,
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...."
},
"question": {
"id": "a44252c3-ef3d-4156-90d6-a3d6364516c0",
"type": "input",
"title": "How can we improve your experience?"
},
"answer": {
"fields": [
{
"id": "c71dfe9d-6b47-452f-bda2-6091c13561b0",
"type": "string",
"value": "The new dashboard is buggy.",
"text": "The new dashboard is buggy.",
"number": null,
"boolean": null,
"time": null
}
],
"replied_at": "2021-07-29T13:45:09.286Z",
"replied_at_ms": 1627566309286,
"tags": ["screeb", "feedback"]
},
"tags": ["screeb", "feedback"],
"items": [
{ "question": {...}, "answer": {...} },
{ "question": {...}, "answer": {...} },
{ "question": {...}, "answer": {...} }
]
}
}
}

Spec

Current version: 2.0.0

When the destination server replies with a 4xx or 5xx status code, Screeb platform sends a new request after 60s (once).

Metadata

FieldTypeOptionaldescription
event_iduuidNoUnique id for this event (same id in retry request)
event_typeenum: response.displayed, response.answered or response.endedNoType of event sent over webhook
timeDateNoDate of event
time_mslongNoTimestamp of event in millisecond
versionstringNoSemver version of the webhook payload
payload<Payload>NoSee "Payload" section

Payload

FieldtypeOptionaldescription
organizationOrganizationNoOrganization details
channelChannelNoChannel details
surveySurveyNoSurvey details
userUserNoUser details
responseResponseNoResponse details

Organization

FieldtypeOptionaldescription
iduuidNoUnique identifier for the organization
namestringNoName of the Screeb account

Channel

FieldtypeOptionaldescription
iduuidNoUnique identifier for the channel
typestringNoChannel type (widget, android, ios, hosted-page...)

Survey

FieldtypeOptionaldescription
iduuidNoUnique identifier for the survey
namestringNoName of the survey
scenario_idstringNoUnique identifier for the survey scenario
scenario_versionintNoVersion number of the survey scenario
scenario_timeDateNoDate of the scenario edition
scenario_time_msintNoTimestamp of the scenario edition (millisecond)

User

FieldtypeOptionaldescription
anonymous_iduuidNoScreeb identifier for the user
user_idstringNoMain user identifier
namestringYesUser name (when available in identity properties)
emailstringYesUser email (when available in identity properties)
group_namesArray<string>YesUser groups

Response

FieldtypeOptionaldescription
iduuidNoUnique identifier for the response
localestringYesLocale of the respondent
timeDateNoDate of response start
time_mslongNoTimestamp of response start in millisecond
time_to_complete_secondlongYesSeconds between survey display and response end (when event_type == response.ended)
completionstringYes"not_started", "partially_completed" or "fully_completed"
hidden_fieldsobjectYesKey/Value of hidden fields
questionQuestionYesSee the "Question" section (when event_type == response.answered)
answerAnswerYesSee the "Answer" section (when event_type == response.answered)
itemsItem[]NoSee the "Item" section (when event_type == response.answered or event_type == response.ended )

Question

FieldtypeOptionaldescription
iduuidNoUnique identifier for the question
typestringNoType of question
titlestringNoLabel of the question

Answer

FieldtypeOptionaldescription
fieldsField[]NoList of values. See the "Field" section
replied_atDateNoDate of the answer
replied_at_msintNoTimestamp of the answer (millisecond)

Field

FieldtypeOptionaldescription
iduuidNoUnique identifier for the field
typestringNoValue type: "string", "number", "time", "boolean"...
valuestringNoPrintable value of the field (always string)
textstringYesValue of the field, when type is "string"
numbernumberYesValue of the field, when type is "number"
booleanstringYesValue of the field, when type is "boolean"
timestringYesValue of the field, when type is "time"

NPS, CES and CSAT will be sent as numeric values, instead of emojis.

Item

FieldtypeOptionaldescription
questionQuestionNoSee the "Question" section
answerAnswerNoSee the "Answer" section

Security

To protect your server from unauthorized webhook events, we strongly recommend that you use HMAC signatures.

Each webhook event will include a signature calculated using a secret key (available on Screeb platform) and a payload from the webhook. By verifying this signature, you confirm that the webhook was sent by Screeb, and was not modified during transmission.

The signature is provided in the headers in this way:

x-screeb-hmac-digest: HMAC-SHA256-BASE64
x-screeb-hmac-signature-url: k8TREZiVCkqywuUT1Lmxa4exhXOf0IS24ibxDIJ3ka8=
x-screeb-hmac-signature-body: uGv+PueUDB/r+3r7/NMfui8wMDerRynN95BacNbGntpE/G7aAi9FNqjYe51ENbFG/d7o3X5uS40ixPNNP1hP/Q==

Here is an example of a signature validation in NodeJS:

function computeHash(secret, payload) {
var crypto = require('crypto');
var hmac = crypto.createHmac('sha256', secret);
hmac.write(payload);
hmac.end();
return hmac.read().toString('base64');
};

function hashIsValid(secret, payload, verify) {
return crypto.timingSafeEqual(verify, computeHash(secret,payload));
};

function webhookHandler(req, res) {
var hmac = req.header('x-screeb-hmac-signature-body');
var body = req.body;

var ok = hashIsValid(process.env.SCREEB_SECRET, body, hmac);
if (!ok) {
res.status(403);
res.send({message: "invalid signature"});
return;
}
}

Support

If you have any questions or additional requirements, feel free to open an issue or contact [email protected].