Webhooks
On the paid plan, TellsU can post each piece of feedback to a webhook as it arrives: for Zapier, Make, n8n or your own systems. Add one in your app’s Forwarding tab. Send a test from there to see a delivery with made-up content.
The request
Section titled “The request”POST https://your-receiver.example/tellsuContent-Type: application/jsonUser-Agent: TellsU-Webhooks/1webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4Wwebhook-timestamp: 1800000000webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4={ "type": "submission.created", "version": 1, "app": { "id": "6f1c2d0e-…", "name": "Tab Shelf", "platform": "extension" }, "submission": { "id": "0b6c9a7e-…", "created_at": "2026-11-02T10:00:00Z", "source": "widget", "feedback_type": "bug", "sentiment": "sad", "message": "Tabs vanish after a restart.", "reply_email": null, "app_version": "2.4.1", "screen": "popup", "env": { "app_version": "2.4.1", "browser": "Chrome 130", "os": "macOS 15", "channel": "beta" }, "custom": { "sync": "on" }, "answers": [ { "question": "Which features?", "type": "multiple_choice", "answer": ["Groups", "Sync"] }, { "question": "Can we contact you?", "type": "yes_no", "answer": "no" } ], "widget_version": "0.1.0" }}Fields
Section titled “Fields”| Field | What it is |
|---|---|
type |
Always submission.created for now |
version |
The payload’s version, 1 |
app.id, submission.id |
The same IDs as in your dashboard and CSV export |
app.platform |
extension, wordpress or custom; accept values you don’t know, as more may come |
source |
widget or link |
feedback_type |
bug, idea, question or praise |
sentiment |
happy, neutral, sad, or null when your user didn’t choose one (never neutral) |
reply_email |
The address your user typed, or null |
env |
The environment, by name; always an object, possibly empty |
custom |
Your own environment values; always an object, possibly empty |
answers |
Your own questions, in order, with their wording when the feedback arrived. type is short_text, long_text, choice, multiple_choice or yes_no; answer is a string, a list for multiple choice, "yes" or "no", or null when skipped |
widget_version |
The widget’s version, or null for a link |
test |
true on a test from the dashboard; absent on real deliveries |
Every value from your user is a plain string, exactly as received. Escape it wherever you show it.
Fields may be added to version 1. Renaming, removing or changing a field
would come as version: 2, sent only if you opt in.
Checking the signature
Section titled “Checking the signature”Deliveries are signed as Standard Webhooks,
so you can use its libraries for most languages. Your secret, starting
whsec_, is in the Forwarding tab, where you can rotate it.
import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.TELLSU_WEBHOOK_SECRET);const payload = wh.verify(rawBody, headers); // throws if it doesn't matchTo check it yourself, webhook-signature is v1, followed by the
base64 HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<raw body>, keyed
with the base64-decoded part of the secret after whsec_. Compare in
constant time, and reject timestamps more than five minutes old.
Delivery
Section titled “Delivery”- Respond with any 2xx within 10 seconds.
- Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then given up. The Forwarding tab shows the latest result.
webhook-idstays the same on every retry, so you can skip repeats.- Only https, to public addresses. Redirects aren’t followed.
- If you move to the free plan, your webhook is kept but nothing is sent until you upgrade again.