Besides email and Slack, an alert can call an address in your own system. That is how you open a ticket automatically, store the event in your database or trigger a flow in Zapier, Make or n8n β they all accept incoming webhooks, no dedicated integration needed. This article's example is the "NPS critical" alert on the post-support survey.
How to set it up
- In Alerts, click New Alert (or edit an existing one). Only the account administrator can set up alerts.
- Fill in Alert Name, Condition and Survey, just like any other alert.
- Under Notification Methods, turn on Webhook and enter the Endpoint URL.
- Save and open the alert for editing again: the Signing secret shows up (it starts with
whsec_), with a Copy button. Store it in your system β it is how you confirm the call came from us.
URL requirements
- It must use HTTPS and be at most 255 characters long.
- It must be reachable from the internet. Internal addresses (
localhost,192.168.x.x,10.x.x.x,.localdomains) are rejected on save. - Do not answer with a redirect: the call goes only to the address you entered, and a redirect response counts as a failure.
What we send
A POST with the Content-Type: application/json and Signature headers, and a body in this format:
{ "event": "alert.triggered", "alert": { "id": "12", "name": "NPS critical", "condition": "nps_below", "threshold": 30 }, "survey": { "id": "01J9Z3K8TQ4M2X7V5B6N0P1R2S", "title": "Post-support" }, "context": { "current_nps": "12", "threshold": "30", "window": "Last 30 days", "responses": "48" }, "triggered_at": "2026-07-27T15:34:56+00:00" }eventis alwaysalert.triggered.conditionis a stable code, not translated text:nps_below,nps_above,sentiment_negative,no_response,response_rate_below,new_feedbackorquestion_average_below. You can use it in aswitch.alert.thresholdis an integer, ornullfor conditions without a value.surveyis the survey that fired. On an alert for all surveys, it is the first one that met the condition.triggered_atis ISO 8601 with the time zone offset β read the offset instead of assuming the time zone.
The context keys
Every value in context is text (a string). The keys depend on the condition:
nps_belowandnps_above:current_nps,threshold,window,responses.question_average_below:question(the question text),current_average(one decimal place, like"2.4"),threshold,window,responses.sentiment_negative:count,latest_comment(up to 180 characters),window.no_response:last_response(date asYYYY-MM-DD),days_since(βif there was never a response),threshold.response_rate_below:current_rateandthreshold(with the % sign, like"12.5%"),sent,responses.new_feedback:comment(up to 200 characters),score(score and scale maximum, like"4/10"; absent when the response has no score, as in a questionnaire with no question counting in the NPS),when(date and time, no time zone).
Note: condition and the context keys are stable codes, but some values come as text in the account owner's language β window, latest_comment when there is no comment and last_response when the survey never got a response. Do not compare those values as if they were codes β to decide, use condition.
Verifying the signature
The Signature header is the hex HMAC-SHA256 of the request body using your signing secret. Compute it over the raw body, exactly as received β if you decode the JSON and encode it again, accents and slashes change form and the signature will not match. Check it before acting on the content: otherwise anyone who finds your URL can forge an alert.
In PHP:
$rawBody = file_get_contents('php://input'); $expected = hash_hmac('sha256', $rawBody, $yourSecret); if (! hash_equals($expected, $_SERVER['HTTP_SIGNATURE'] ?? '')) { http_response_code(403); exit; }In Node.js (with the raw body in rawBody):
const expected = crypto.createHmac('sha256', yourSecret).update(rawBody).digest('hex'); const received = req.get('Signature') || ''; const valid = received.length === expected.length && crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));When it arrives and retries
The webhook follows the same pace as any alert: conditions are checked every 30 minutes and, after firing, the same alert waits 6 hours before calling again.
Any 2xx response counts as delivered. Each call has a 5-second limit; if your endpoint is slow, errors or redirects, we make up to 3 attempts in total, with increasing waits between them. Answer fast (an immediate 200) and process later, in the background. If all three fail, the alert card shows Last delivery failed; hover over it to see the destination and the reason (for example, Webhook: HTTP 500). The warning clears on its own when a later webhook delivery succeeds; a successful Slack delivery does not clear the webhook failure, and vice versa.
There is no test-send button. To see a real call, create a No response for alert with value 1 on a survey that has had no responses in the last day: it fires at the next check.
Privacy
The payload does not identify who answered: the comment and the score are sent, the name and email are not. On an anonymous survey, that is a promise made to the respondent, and it holds here too.
Changing the URL or removing it
Changing the address keeps the same signing secret, so you do not need to reconfigure verification on your side. Turning Webhook off removes the URL and deletes the secret β if you turn it back on later, a new secret is generated.
Where to go next
- Alerts β the conditions and how often each one warns.
- Receiving alerts in Slack β the same warning in the team's channel.
In short: turn on Webhook in the alert, enter a public HTTPS URL, store the signing secret and validate the Signature header over the raw body before acting.