External flows and hooks
Flows can reach automation you already run somewhere else, in both directions:
- Out: a step called Start an external flow sends a request to a Power Automate flow, a Zap, a Make scenario or an n8n workflow, through a connection. It can wait for that app to post an answer back.
- In: a flow whose trigger is Another app calls it has one or more addresses, each with its own token. Another app starts the flow by posting to one.
Both are whole-school integrations, so both need the Integrations module on. Neither works on the public demo: nothing is sent to another app there, and no address can be made.
For a flow that calls Entra ID, Google Workspace or your device management, you do not need any of this. Flows call those directly through their connectors (see building a flow).
Connections
Section titled “Connections”Flows, Connections tab. The tab shows for people who hold flow.publish
and integration.manage. Adding, changing or removing a connection also needs
connector.manage, and asks you to confirm it is you (step-up).
A connection is the address the other app gave you for starting its flow, plus what that app may be sent. To add one, choose Add a connection:
| Field | What it holds |
|---|---|
| Name | Something you will recognise, such as “Power Automate: staff joiners” |
| Address | The full https:// URL the other app gave you |
| What it may be sent | Extra kinds of data beyond ids and plain details (below) |
| Sign what it sends | Standard Webhooks headers on every request. On by default |
| Extra headers | Up to 10, such as an Authorization header the app asks for |
The address is write-only
Section titled “The address is write-only”The address is sealed in the vault when you save it and never shown again in
full. A Power Automate sig or a Zap’s path is a credential in its own right,
so the list shows only the host and the last four characters. The address
never appears in run history, events, the AI ledger or the audit log.
A connection is bound to its host. You can change the address later, but only to another address on the same host. An address on a different host is a different connection: add it as one, so it gets its own review. Extra header values are sealed the same way; a saved header can be replaced by name but not read back.
Plugboard refuses an address that:
- is not
https://; - has a user name or password in it (put a password in a header instead);
- is not a public internet address (loopback, private, link-local and cloud metadata addresses are all refused).
When a request is sent, a redirect is never followed, the other app has 10 seconds to answer, and the request is never retried by itself.
While you type the address, Plugboard names the platform from its host and
warns you about known traps, such as a Power Automate address with no sig
(set Who can trigger the flow to Anyone in Power Automate), a Logic Apps
address pasted by mistake, or an Apps Script address (Apps Script cannot read
headers, so add a long random token to the address and check it in the
script).
What a connection may be sent
Section titled “What a connection may be sent”Every connection may be sent ids and plain details. Three kinds of data are off until you tick them on that connection:
| Choice | Covers |
|---|---|
| Ticket text | Subjects, descriptions and replies, which people write and may hold anything |
| Names and addresses | People’s names and email addresses |
| Anything about students | Values from a flow that can be about a student |
These are checked twice: when a flow is published (a flow that sends more than its connection allows cannot be published) and again when the step runs, in case the connection changed since.
Signing
Section titled “Signing”With Sign what it sends on, Plugboard adds webhook-id,
webhook-timestamp and webhook-signature headers in the
Standard Webhooks form, using a signing
secret shown once when you save. An app that checks them knows the request
came from your Plugboard. Turn it off only if the app refuses unknown headers.
Removing and testing
Section titled “Removing and testing”Send a test posts a small test body ({"test": true, "from": "Plugboard", ...}) to the address and says whether the other app accepted it.
Each test is recorded in the audit log. Each connection lists the
published flows that use it, and a connection in use cannot be removed: change
those flows first.
The Start an external flow step
Section titled “The Start an external flow step”Add Start an external flow to any flow and choose a connection. Under What it is sent, give each value a name the other app reads it by, and fill it from the trigger or an earlier step. A value the connection may not be sent is flagged on the card at once.
The request is a POST of a JSON object made of those named values, plus a
plugboard object with the run’s id and the flow’s name, so the other app can
say which run it is answering about:
{ "reason": "Projector in B12", "plugboard": { "runId": "...", "flow": "AV faults to the contractor" } }A named value called plugboard cannot replace it. It carries no person or
ticket text, so it needs no data class. The request is sent with:
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
Plugboard-Flows/1 |
Idempotency-Key |
The same on every attempt of this step, so the other app can drop a repeat |
webhook-id, webhook-timestamp, webhook-signature |
When signing is on |
Plus any extra headers you saved. Records are sent as their ids, text is cut to 4,000 characters and lists to 200 items. The body is limited to 64 KB.
Waiting for an answer
Section titled “Waiting for an answer”Turn on Wait for its answer and list What the answer carries. Plugboard
then adds a callbackUrl value to the request: an address that works for this
step only. The other app posts its answer there once, as a JSON object:
curl -X POST "$CALLBACK_URL" \ -H "Content-Type: application/json" \ -d '{"status": "done"}'The address:
- works once, then refuses;
- expires when the step gives up (Give up after, at most 7 days);
- takes at most 64 KB, and only plain values;
- is checked against what you said the answer carries.
The answer is marked “From another app”. It can choose a branch and appear in messages, but it can never choose who or what a later step changes. If nothing comes back in time, the step follows its If this fails setting.
Waiting needs Plugboard to know its own web address: whoever runs Plugboard
sets PUBLIC_URL (see environment variables).
Who it acts as
Section titled “Who it acts as”The step acts as the person who started the run, or otherwise as the flow within its owner’s live access. It counts toward the school’s hourly automation limit; when that is reached, the step waits 15 minutes and tries again. If the other app refuses the request (a 4xx other than 408 or 429), the step fails with that status and is not tried again by itself. A 408, 429 or 5xx is said to be a failure that may pass.
Another app calls it
Section titled “Another app calls it”Choose Another app calls it as the trigger. Under What it sends, list the values the request carries. Anything else in the request is dropped, types are checked, and text is cut to a sensible length. Everything it sends is marked “From another app”: it can start the flow and be used in messages, but never choose who or what a step changes without a person.
Once the flow is saved, Addresses lists where it can be called.
Managing addresses needs flow.publish and integration.manage, the
Integrations module on, and step-up for every change.
Tokens
Section titled “Tokens”Add an address asks which app will use it, and makes a token that starts
pbh_. The token is shown once, with copy buttons and two recipes (curl and
Power Automate). After that, only its first characters show. Plugboard keeps a
hash, never the token.
Give each app its own address, so one can be revoked without the others.
- Make a new token rotates it. Choose how long the old token keeps working: Stops at once, Keeps working for an hour or Keeps working for a day, so you can update the other app without missing a request.
- Revoke stops the token at once. Anything still using it is refused.
Tick Also require a signature for senders that can sign, such as n8n or a script. The address then gets a Standard Webhooks signing secret of its own, shown once. Power Automate and Zapier cannot sign.
Calling it
Section titled “Calling it”curl -X POST "https://plugboard.example.edu.au/api/v1/flow-hooks/<hookId>" \ -H "Authorization: Bearer $PLUGBOARD_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"reason": "Projector in B12"}'The rules, in the order they are checked:
| Check | Refused with |
|---|---|
| The body is at most 64 KB | 413 |
Authorization: Bearer pbh_... is present and valid for this address |
401 |
| At most 60 requests a minute for this address | 429 |
| Flows are on for the school | 403 |
| The flow is published and on (or Watching) | 409 |
| The body is a JSON object | 400 |
An Idempotency-Key header of at most 200 characters |
400 |
With signing: webhook-id, webhook-timestamp (within five minutes) and a matching webhook-signature |
401 |
| Every value the flow marks as required is present | 400 |
A request that passes starts one run and answers 202 with its id:
{ "runId": "..." }A repeat with the same Idempotency-Key within 72 hours starts nothing and
answers with the first run’s id, so retries are safe. With signing on, a
captured request sent again under a different key is refused by its webhook-id.
Each run another app starts writes one audit entry naming the address, never the body or the token.
Checking a run
Section titled “Checking a run”curl "https://plugboard.example.edu.au/api/v1/flow-hooks/<hookId>/runs/<runId>" \ -H "Authorization: Bearer $PLUGBOARD_TOKEN"It answers where the run is and nothing about what it holds:
{ "runId": "...", "status": "waiting", "finished": false }status is one of checking, proposed, waiting, running,
needs attention, done, stopped or not matched. finished is true for
done, stopped and not matched. It only answers for runs that address
started.
Related
Section titled “Related”- Zapier
- Outbound webhooks, for desk events without a flow
- Schedules and calling flows
- Flows overview