Three ways to press a flow from outside chat
There are three ways to run a flow's Stream Deck button trigger, or reach a flow's private URL, from outside chat:
- The Bloopbot Stream Deck plugin. A beta companion app for Elgato Stream Deck, downloaded from your dashboard: sign in once, and a key presses exactly the one Stream Deck trigger you assign to it. There's no URL to keep secret. Recommended for a Stream Deck key. See Run flows from a Stream Deck.
- The Stream Deck app's own Website action, with a flow's private URL. Works today, on any Stream Deck, without the plugin. It calls the flow's URL as a plain web request, so it runs every URL trigger in that flow, not only the one you meant. Covered below.
- A script or a phone shortcut, calling the same private URL. Covered below.
Two directions
A flow can talk to the rest of the internet both ways:
- In: the On a web request and Stream Deck button triggers give a flow its own private URL. Pressing a Stream Deck key, tapping a phone shortcut or running a script calls that URL, and the flow runs — for example, a key that posts your Discord link in chat.
- Out: the Web request action calls a web address of yours from inside a flow — to ping a service when you go live, or fetch a line of text to post.
Both are listed under Automation in Plan & billing, as Webhook triggers and Outbound requests. Webhook triggers are included on every plan, and outbound requests in Free+, Plus and Pro; when your channel does not include one, its node shows a lock in the library. What your plan includes explains the page.
1. Get a flow’s URL
The quickest start is the Stream Deck button flow in the Starter pack: it already has a Stream Deck button trigger, and it posts whatever message the caller sends. To build your own, add Stream Deck button for a key or On a web request for another caller from the node library’s Integrations topic. Both currently use the same private URL for that flow. A Stream Deck key can call it with the Website action set to GET request in background — no plugin required, though it calls every URL trigger in the flow rather than a single chosen one; the Bloopbot Stream Deck plugin presses one specific trigger instead.
- Open the flow and press Save once. A new flow has no URL until its first save; until then the trigger says Save this flow once and its URL appears here.
- The trigger’s Your URL field now shows the address. Select the copy icon (Copy URL).
- Turn the flow on. A paused flow does not answer its URL — pack flows arrive paused.
- Paste the URL into the tool that will call it — a Stream Deck action that calls a web address, a phone shortcut or your own script.

Anyone who has the URL can run the flow, so keep it private. The rotate icon (Rotate URL) makes a new URL and the old one stops working at once. The broken-link icon (Revoke URL) removes the URL entirely until you save the flow again or choose Create one. Removing every URL trigger from the flow removes its URL too. If a flow has both an On a web request trigger and a Stream Deck button trigger, one call to the shared URL starts a run from each of them — this is a property of calling the URL, not of the Bloopbot Stream Deck plugin, which always presses one specific trigger and nothing else in the flow.
Make a key graphic
The Stream Deck button trigger can keep an image with the flow. In its Key graphic field, choose an image, press Upload image, or type a short label and press Make simple graphic. The simple option makes a square PNG with white text on a blue background. For example, enter Discord for a key that posts your Discord link. You can clear the field with No graphic.
The picker starts on Stream Deck graphics, showing only images tagged for keys. Uploads and simple graphics made here get that tag automatically. Choose All images to pick one already in your media library; choosing it tags it as a Stream Deck graphic too. The tag makes the image easier to find later and does not remove it from the regular media library. A graphic imported in a bundle is tagged the same way.
After saving the flow, choose Open graphic for your Stream Deck key and save the image. In Stream Deck, set that image on the same Website action that calls the flow URL. The image does not change the URL or fire the flow by itself. The flow stores the image so you can find it again; if you use the Bloopbot Stream Deck plugin instead, it picks up the same graphic on its own.
If you export the flow or select it for Export bundle, the image goes in the .bloop.zip file with it. Whoever imports that file gets a pack with their own copy of the image, but must still set up their own Stream Deck key and use the new flow URL. Share links cannot carry media files; use the bundle file when sharing a graphic. Share, import and restore flows explains bundle files.
2. Send it some details
A call can carry details for the flow to use. Each one becomes a token named $(hook. plus its key — a key called message is $(hook.message).
- POST a flat JSON object, such as
{"message": "Discord is open!"}. - Or GET the URL with query parameters, such as adding
?message=helloto the end.
For example, from a terminal, with your copied URL in place of the placeholder:
curl -X POST "PASTE-YOUR-FLOW-URL-HERE" -H "Content-Type: application/json" -d '{"message": "Discord is open! discord.gg/example"}'The Stream Deck button flow sends $(hook.message) to chat, so this posts Discord is open! discord.gg/example. Keep details simple:
- Up to 20 keys of up to 64 characters, named with letters, digits and underscores. Keys are read in lower case, so
Messagearrives as$(hook.message). - Each value is text, a number or true/false, up to 200 characters. Nested objects and lists are refused.
- The whole body is at most 4 KB.
- A key the caller did not send comes out empty.
3. Require a secret (optional)
By default the trigger says Anyone with the URL can fire this flow. For a script you control, choose Require a secret. The secret is shown once, so copy it straight away; the trigger then reads Signed requests required.
Each call must then carry an HMAC-SHA256 signature of the exact request body — for a GET, of the query string after the ? — made with the secret, sent as a hex string in the X-AWRA-Signature header. A sha256= prefix is accepted. Calls without a valid signature are refused. The key icon (New secret) replaces it, and the bin icon (Remove secret) goes back to URL-only.
A Stream Deck key or phone shortcut usually cannot sign requests. For those, keep the URL private and rotate it if it leaks.
Check it works
- Test fire checks the flow’s steps without calling the URL. A test has no caller, so the trigger reports no payload in the Run log and every
$(hook.…)token comes out empty — the Stream Deck button flow would send an empty message. Nothing is posted. - Call the real URL — press your Stream Deck key, or run the command above. This runs the flow for real: its chat message is posted in your chat.
- A successful call answers
202with{"ok": true}straight away, before the flow finishes. Open the flow’s Run log to see what it did.
Call a service from a flow
The Web requests and Secrets buttons appear when your plan includes Web requests, or you have an installed marketplace flow with a request that still works on every plan. A paused flow counts, so you can set it up before turning it on. On Free without a qualifying flow, both buttons are hidden. Removing the last qualifying flow or changing all its exempt requests hides them again; your saved secrets stay with your channel.
On Free, a Web request step's panel says Free+ unlocks this: join our Discord beside the upgrade. Join the Discord opens in a new tab, so your flow stays where you left it: it adds you to the Bloopbot Discord and puts your channel on Free+, which runs web requests within a daily and a monthly limit. Not now hides it for 30 days. Unlock Free+ by joining the Discord has those limits.
- On the Flows page, the header button reads Web requests: off until you allow them. Choose it so it reads Web requests: allowed. This is one switch for your whole channel.
- In a flow, add the Web request action from the Integrations topic.
- Fill in URL (
https://only;$(...)tokens are filled in), Method — GET (fetch) or POST (send a body) — and, for a POST, Body (POST). A body that is valid JSON is sent as JSON; anything else is sent as plain text. - To use the answer, set Keep the answer as to a short name such as
weather. Later steps read the reply as$(local.weather)and its status code as$(local.weather_status), or through Insert… under this step’s name. To read inside a JSON answer, use Read JSON below instead: it reads the whole answer, while the kept copy holds only its first 4,000 characters. - Wire the step’s ok output to what should happen next, and its bad answer output to a fallback, such as a chat line saying the service is down.
For example, a !weather command that fetches a plain-text forecast:
| Field | Value |
|---|---|
| URL | https://weather.example/today?format=text |
| Method | GET (fetch) |
| Keep the answer as | weather |
| Next chat message | Today: $(local.weather) |
Test fire never makes the request: the Run log shows the address it would call, and the answer reads (dry run). Only a real run reaches the service.
Send headers a service asks for
Some services refuse a request that does not carry headers of their own, such as a name for your app, a way to contact you, or a key in Authorization. Their documentation says which ones they need.
- In your Web request step, open More options and find Headers.
- Choose Add a header. Type the header’s name in the left box, such as
X-Api-Key, and its value in the right box. Insert… beside the value adds$(...)tokens, just as in the URL. - Add one row per header, up to 10, then save the flow.
For example, the Helldivers 2 community API answers 400 with “The X-Super-Client and X-Super-Contact headers are required” until each call names your app and a way to reach you:
| Field | Value |
|---|---|
| URL | https://api.helldivers2.dev/api/v1/war |
| Method | GET (fetch) |
| Headers | X-Super-Client → a name for your bot, such as bloopbot-yourchannel; X-Super-Contact → your email address or Discord name |
With both headers set it answers 200. Add Read JSON with a Pick row such as players → statistics.playerCount, then a chat message that inserts players from under the Read JSON step’s name.
- For a key, write a secret in the value, such as
Authorization→Bearer $(secret.api_key). The secret rules are the same as for the URL: type the address in full up to the first/. - Bloopbot sets some headers itself, and saving refuses them in Headers:
Host,User-Agent,Content-Length,Transfer-Encoding,Connection,Keep-Alive,Upgrade,TE,Trailer,Expectand anyProxy-…header.Content-Typeis allowed and replaces the type Bloopbot picks from the body. - A header name is letters, digits and
-,_or., up to 64 characters, and each name appears once (capitals don’t matter). A value holds up to 1,000 characters as typed. A line break that a viewer’s text brings in is sent as a space. - Test fire lists the headers a step would send by name only, never their values.
- An exported flow holds each Web request’s headers along with its URL and body, so keep keys in Secrets.
Keep an API key out of your flow
Many services want a key in the address, like ?key=…, or in a header. Written straight into the step, anyone who can edit your flows sees it, and so does your Run log. Save it as a secret instead, and the flow only holds its name.
- On the Flows page, choose Secrets beside Web requests. If neither button appears, your channel needs a plan with Web requests or an installed flow with a request that works on every plan.
- Under Add a secret, type a Name such as
weather_key— lowercase letters, digits and underscores, starting with a letter. - Paste the key into Value and choose Save. The name appears in the list with the day you saved it.
- In your Web request step, write
$(secret.weather_key)where the key goes in the URL, Body (POST) or a Headers value, for examplehttps://weather.example/today?key=$(secret.weather_key). - Run the flow for real. The step sends the key; the Run log names only the service’s address, such as
200 from weather.example.

- A value is never shown again once saved, not even to you. Bloopbot never writes it to chat or the Run log, unless the service you call repeats it in its answer.
- To change a key, choose the pencil icon (Replace) on its row and paste the new value; every flow that uses the name picks it up on its next run. The bin icon deletes it after you confirm.
- Secrets work only in a Web request’s URL, Body (POST) and Headers values. Anywhere else,
$(secret.…)goes out as typed, and saving the flow warns you. - When a step uses a secret, type the address in full up to the first
/, as inhttps://weather.example/…, with no$(…)in it. Put the secret after that, in the path or the?…part. Saving refuses a secret in the address itself, such ashttps://$(secret.name).example.com, and any$(…)in the address of a step that uses a secret. That is what keeps your key safe from chat: text a viewer types or a webhook sends can never fill in a secret or choose the site it goes to. - Test fire never uses a secret: it hides every
$(secret.…)behind dots and does not check the name, so a misspelt name still looks fine there. Check a new name with a real run: the Run log says “Secret … is not set on this channel.” when the name is wrong. - A channel holds up to 20 secrets, each up to 4,096 characters. Secrets stay with your channel: exporting or sharing a flow never includes them.
- Anyone who can edit and run your flows, such as an editor or an AI assistant you connected with write access, can send a secret to any
https://address they put in a Web request. Tying a secret to the one site it belongs to is planned.
What the bot sends
A Web request from a Bloopbot flow reaches your site as a plain GET or POST over https:// on port 443, and identifies itself with this user agent:
Bloopbot-flows/1 (+https://bloopbot.com/docs/webhooks#abuse)- It waits at most 2 seconds for an answer, reads at most 64 KB of it, and does not follow redirects.
- It never reaches a private or local network address.
- One flow run makes at most 2 requests on Free+ or Plus, or 20 on Pro (also 20 on Legacy), and one channel makes at most 60 a minute, however many viewers trigger it. Past that, the step leaves by bad answer with the reason “This channel has made too many web requests in the last minute”, and nothing is sent.
- A channel on Free+ sends web requests in at most 50 flow runs a UTC day and 200 a UTC month. Past that, the step leaves by bad answer with status
0and nothing is sent. - It sends no cookies and no account details of its own. The request holds only what the streamer put in the flow’s URL, headers and body.
A streamer chooses which addresses their flows call. If you run a site that receives these requests and want them to stop, you can block the Bloopbot-flows user agent. To report a channel, or to ask us to look at a request you did not expect, email [email protected] with the time, the address that was requested and the request’s source address if you have it.
Read a JSON answer
Most services answer in JSON. Read JSON picks the values you want out of it, so a chat line can use them and For each can go through a list.
- After your Web request, add Read JSON from the Integrations topic and wire the web request’s ok output to it.
- Set Read to the web request’s answer: choose Insert…, then Response text under the web request’s name. It reads
$(from.<step>.text). - Under Pick, add a row per value: a short name, then the path to it. Each name becomes a value later steps can insert.
- Wire ok to what happens next, and not found to a fallback line.
Paths follow the answer’s own keys, separated by dots:
| The answer | Path | You get |
|---|---|---|
{"current":{"temp_c":14}} | current.temp_c | 14 |
{"items":[{"name":"Sword"}]} | items[0].name | Sword (positions count from 0) |
{"data":{"2":{"high":288}}} | data.2.high | 288 (a number on an object is a key) |
{"data":{"2":{…},"6":{…}}} | data.* | a list, one item per entry |
- Keys are case-sensitive in a path:
highTimeis nothightime. - A path can use a value from the command:
data.$(1).highreads the item the viewer typed. - A pick that holds a list also gives How many in …, its length. The list itself keeps its first 50 items, while How many in … still counts them all.
- A pick can’t be called
all, and put Read JSON before any Wait: a Wait keeps only the first 4,000 characters of an answer.
Going through a list. Set For each’s Go through to the pick, such as $(from.<step>.entries). Inside the loop, $(item.<field>) reads each item’s fields. Nested fields are joined by _, so user → name becomes $(item.user_name), and capitals don’t matter there: $(item.highTime) and $(item.hightime) both work. An item from * also has $(item.key), its entry’s key.
Turn a timestamp into words
Services often give times as a long number, such as 1790721171. Format a time turns one into words.
- Add Format a time from the Integrations topic.
- Set Time to the timestamp, such as
$(item.hightime). It reads seconds, milliseconds or a date like2026-09-29T20:14:00Z. - Pick a Clock: 24-hour or 12-hour.
- Choose Insert… in a later step to use what it made: How long ago (
2 minutes ago), Clock time (20:14), Date (29 Sep), Day of the week, Day, date and time, or Minutes ago to compare in a check. - Wire ok onward. Text that isn’t a time leaves by not a time.
Times and dates use your channel’s time zone, the same one your weekly timers use: the one you pick when you add a stream on the Schedule page, or, if you have never picked one, the time zone of the browser you first opened your dashboard in. If neither is known, they are in UTC. They are always in English.
For example, a !prices command that posts each item’s last high price and when it traded:
| Step | Setting |
|---|---|
| Web request | URL https://prices.example/latest, GET (fetch) |
| Read JSON | Read $(from.<web request>.text); Pick entries → data.* |
| For each | Go through $(from.<read json>.entries) |
| Format a time | Time $(item.hightime) |
| Send a chat message | Item $(item.key): high $(item.high), $(from.<format a time>.relative) |
Wire For each’s each output to Format a time, and Format a time’s ok to the chat message, so they run once per item. Chat shows one line per item, such as Item 2: high 288, 2 minutes ago.

Troubleshooting
- The URL answers
404: the flow is paused, the URL was rotated or revoked, or the flow was deleted. Turn the flow on, or copy its current URL again. A flow restored from the Trash comes back paused, with a new URL. 429: too many calls. One URL takes up to 10 calls a minute, and your channel up to 30 across all its URLs. Wait a minute.401: the flow requires a secret and the signature is missing or wrong. Sign the exact body you send.400or413: the details are not flat JSON, break one of the limits above, or the body is over 4 KB. The answer says which.- The flow ran but the message is blank: the caller did not send the key the message uses. Match the key names exactly, such as
message. - The key shows no picture: save the image opened from Key graphic, then set it on the Website action in Stream Deck. Choosing an image in the flow does not change the physical key automatically.
- The Web request step always leaves by bad answer: check Web requests: allowed on the Flows page, that the address starts with
https://, and the Run log’s reason, such as no answer within 2 seconds or a redirect, or the channel having made 60 web requests in the last minute; it recovers by itself as the minute passes. - On Free+, a Web request that worked earlier now leaves by bad answer with status
0, and the Run log says the channel has used its flow runs with web requests for today or this month: that's a Free+ limit. The rest of the run still carries on. Requests start again at midnight UTC, or on the 1st for the month's limit. - The service answers
400,401or403and names a header it needs: add that header under the step’s Headers, or check the spelling of one you added. - The Run log says “Secret weather_key is not set on this channel”: the name in the step does not match one under Secrets, or it was deleted. Check the spelling, or add it again. No request was made.
- Read JSON always leaves by not found: open the Run log. “The answer isn't JSON” means the service answered something else, often an error page; “no … in the answer” names the path it could not find, so check its spelling and capitals against the answer. Test fire makes no web request, so Read JSON after one always leaves by not found there — run the flow for real to test it.
- A time is an hour or more out: Format a time uses the time zone of your Schedule, the one you pick when you add a stream on the Schedule page. Check it there.
- The node shows a lock: your channel does not include that capability yet. Nothing else about the flow changes.
- A key made with the Bloopbot Stream Deck plugin shows Paused, Offline, Reassign or another status instead of pressing: that's the plugin's own key states, covered in Run flows from a Stream Deck.
Was this guide helpful?
Thanks. Your answer helps us decide which guides to improve.