Stream Webhook sends Stream interactions to a URL you choose, as soon as it's saved. Use it to build custom applications with agents and automations.
What you need
- Stream iOS on your iPhone
- An active Upstream subscription
- A URL that starts with
https://and accepts POST requests, like a Slack incoming webhook, a Zapier or n8n catch hook, or your own server
Set it up
- On your iPhone, open Stream and go to Settings → Other Tools → Developer (Webhook)
- Paste your URL into Paste webhook URL
- Turn on Send interactions
- Check the address in the alert, then tap Turn On
- Hold your Ring and say something to test
Tip: To send interactions to your webhook without Stream responding to each one, turn Chat off. Tap the chat bubble in the Stream tab.
Tip: To organize interactions sent to other agents or apps, create a folder inside Artifacts → Notes with the name of that tool.
Options
| Option | Use it when | How |
|---|---|---|
| Send extra headers | Your receiver wants a token | Add one header per line, like Authorization: Bearer your-token
|
| Sign requests | Your receiver checks that requests came from Stream | Copy the secret Stream shows into your receiver. |
Security
Treat your webhook URL like a password. Anyone who has your webhook URL can post interactions to your receiver, and whoever runs the receiver can read everything you say to Stream.
If your URL leaks, make a new one where you created it, then paste the new URL into Stream.
Built the receiver yourself? Turn on Sign requests. Your server can then reject any post Stream didn't send, even if the URL leaks. See Check the signature under For developers.
Receiver wants a token? Turn on Send extra headers and add a line like Authorization: Bearer your-token.
Stream Webhook already supports the following:
- Only sends to
https://addresses - Never follows redirects
- Sends no cookies, no app credentials, and no account info
- Puts no name, email or device ID in the body
- Tries once and gives up after 5 seconds
- Keeps your URL, headers and secret on this phone only. They are never synced or logged, and they are wiped if someone else signs in on this phone.
Try this
Control Agents
Drive your agent with your voice. Paste this prompt into an agent that runs on its own, like Hermes or OpenClaw. It connects itself to Stream, picks out what's meant for it, and gives you a URL and header to paste into Stream.
Set yourself up so I can drive you with my voice through Stream. Stream will POST everything I say to a webhook URL you give me.
What Stream sends:
- An HTTPS POST with a JSON body: {"text": "what I said", "interaction_id": "a3f1..."}
- Every interaction, not just ones meant for you: notes, list items, reminders, questions to Stream.
- Only to https:// URLs. It doesn't follow redirects and waits 5 seconds for a response.
Set it up:
1. Create a webhook endpoint on yourself that turns "text" into a task for you. Use your built-in webhook support if you have it.
2. Add a router so you only act on what's meant for you, and ignore everything else without replying. Rule: [what counts, e.g. "a task I want done, or anything that starts with '(your name)'"].
3. Protect it by checking the X-Stream-Signature header on every request. It looks like t=<unix seconds>,v1=<hex>. Compute the hex HMAC-SHA256 of <t>.<raw request body>, using my full signing secret (including the swh_ prefix) as the key. Reject the request if the result doesn't match v1, or if t is more than 5 minutes from now. Ask me for the secret, and I'll copy it from Stream under Sign requests.
4. Make the endpoint reachable at a public https:// URL, like with Cloudflare Tunnel or Tailscale Funnel.
5. Tell me where your responses will show up. If that's not somewhere I'll see them, like this chat, walk me through connecting a messaging app like Telegram.
6. If webhook runs have limited tools, tell me which ones this needs and how to turn them on.
When you're done, test it with a sample payload and give me the URL to paste into Stream.Post to a Slack channel
No code needed. Stream's request is already a valid Slack message. If your agent lives in Slack, it sees each one as it arrives. Give this prompt to your coding agent:
Walk me through creating a Slack incoming webhook that posts to #[channel name], one step at a time. Stream will send it an HTTPS POST with JSON like {"text": "what I said", "interaction_id": "..."}, and Slack will post the text as a message. At the end, tell me exactly which URL to copy into Stream.Start an automation in n8n or Zapier
Log every interaction to a sheet, or start any workflow. In Zapier, use Webhooks by Zapier → Catch Hook. For n8n, give this prompt to your coding agent:
Write an n8n workflow I can import.
- Trigger: a Webhook node that accepts POST requests with JSON like {"text": "what I said", "interaction_id": "..."}.
- Then: [what should happen, e.g. "add a row to my Google Sheet called Stream log with the time, text and interaction_id"].
Then tell me how to import it, turn it on, and which Webhook URL to paste into Stream (the production URL, not the test URL).FAQ
What gets sent?
Text and an ID for every saved interaction.
- Sent: What you say or type into Stream
- Not sent: Dictation, Stream's replies, audio, or your account information
Why isn't anything arriving?
Check that you have an active Upstream subscription, the URL starts with https://, and Send interactions is on. The last error shows in red on the Developer screen.
Why is my receiver rejecting requests?
If it returns a 401 or 403 error, it wants a token. Turn on Send extra headers and add it.
Does it work offline?
No. Interactions are sent from your device, not Sandbar servers. If your device is offline, the interaction may not be sent.
Does it work on my other devices?
No. The URL and options stay on this device and are removed when another account signs in on it or you delete the iOS app. On a new device, or after either of those, paste the URL again. If you sign requests, turn Sign requests back on and copy the new secret into your receiver.
Can I use an http:// address?
No. Only https:// addresses are contacted, and redirects aren't followed.
Who can see my interactions?
Whoever runs the URL can read every interaction. Only use URLs you trust.
How do I turn it off?
Toggle Send interactions off. To remove the URL, clear the field.
For developers
Request
| Setting | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Sent from | The user's iPhone, over its own network |
| URL |
https:// only. Redirects are not followed |
| Timeout | 5 seconds |
| Retries | None. One attempt per interaction |
Body
{
"text": "Book a table for four on Friday",
"interaction_id": "a3f1..."
}| Field | Type | Meaning |
|---|---|---|
text |
string | What the user said. Never Stream's reply |
interaction_id |
string | Unique per interaction. Use it to skip duplicates |
No name, email or device ID is included.
Headers
| Header | Sent when | Value |
|---|---|---|
Content-Type |
Always | application/json |
X-Stream-Signature |
Sign requests is on | t=<unix seconds>,v1=<HMAC-SHA256> |
| Your extra headers | Send extra headers is on | As entered |
Check the signature
Compute HMAC-SHA256 of <t>.<raw body> with your secret and compare it to v1. Use the raw body, before parsing. Reject requests older than 5 minutes. The signature is hex, and the key is the full secret, including the swh_ prefix.
import crypto from "node:crypto";
// rawBody: the request body exactly as received, before JSON.parse
export function verifyStream(rawBody, header, secret) {
if (!header) return false;
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}What won't change
-
textalways means what the user said. - New fields may be added. Ignore ones you don't recognize.
- New event types will be opt-in. A URL you've already set up won't start getting new kinds of events.
Need help?
Email support@sandbar.com, or in Stream iOS:
Settings → Get Support
We usually reply within one business day.