Think about the last time you wired up a notification for an internal system. You probably treated it as a terminal event: something happened, send a message, done. The deploy shipped, so you notified the channel. The job failed, so you fired an alert. The notification was the period at the end of the sentence.
But the notifications that carry real weight are not the end of anything. They sit in the middle of a process that is paused, waiting on a person, and ready to continue the moment that person answers. The deploy is staged and waiting for a go. The job failed and is waiting to be told retry or leave it. The agent is waiting to be told yes before it spends the money. The human is not the last step. The human is a step, and the workflow resumes after them.
If you model the human as a step, a lot of homemade glue gets to retire.
The loop, not the feed
The mental model that makes this work is a closed loop rather than a one-way feed:
trigger a deploy is staged · a job fails · an agent hits a risky step
↓
request publish into a channel
↓
notify the right people get it on their phones
↓
respond a tap · a form · a reply · an attachment
↓
webhook a signed callback returns to your system
↓
resume your code (or your agent) acts on the decisionThe first half of that loop, trigger to request to notify, is the part every notification tool already does. The second half is the part that turns a message into a step: respond, webhook, resume. Roundtrip is built around the whole loop, so the workflow can run through it instead of falling out of it when a human gets involved.
Channels are where workflows live
A workflow needs to reach the right people, not everyone. That is what channels are for: each one groups a kind of loop by concern, and the people who should answer that kind of ask subscribe to it.
import { content, approveReject } from "@roundtrip/sdk";
// Each channel is a workflow's waiting room.
await roundtrip.requests.create({
channel: "deploys", // release managers watch this one
notification: { title: "api@2.3.0 staged — ship it?" },
content: content({ actions: approveReject() }),
response: { mode: "required", webhook: DEPLOY_HOOK },
});
await roundtrip.requests.create({
channel: "incidents", // on-call watches this one
notification: { title: "Checkout error rate 11% for 4 min" },
content: content({
actions: approveReject({ approveLabel: "Ack", rejectLabel: "Escalate" }),
}),
response: { mode: "required", webhook: INCIDENT_HOOK },
});deploys, alerts, incidents, agent-tasks — each is a place where one kind of workflow parks while it waits on a human. The card carries the context the person needs to decide; the webhook carries their decision back to the pipeline that was waiting.
Resuming on the answer
The piece that makes this a workflow and not just a prettier alert is that your system does something with the response. When the person answers, Roundtrip POSTs a signed callback to your webhook carrying the requestId you started with, the tapped action, any submitted form values, and the metadata you attached. That is your resume point.
// inside your verified webhook handler
if (event.action === "approve") {
await pipeline.promote(event.metadata.releaseId); // the loop continues
} else if (event.action === "reject") {
await pipeline.hold(event.metadata.releaseId); // and so does this branch
}The deploy promotes itself. The incident gets an owner. The job retries. No one watched a channel and clicked a link and then went and did the thing by hand. The human made one decision, and the workflow took it from there.
The call to publish is non-blocking. requests.create returns as soon as the request is out, and the answer arrives later at the webhook, so a process can fan out a dozen of these and resume each one as its human responds, independently.
Stop ending workflows at the notification
The shift is small to say and large in practice: a notification is not where a workflow ends. It is where a workflow waits for a person and then keeps going. Once the answer comes back structured, on its own, into your system, the human becomes a first-class step in the automation instead of a manual detour around it.
The getting-started walkthrough wires the full loop end to end, trigger to request to response to signed webhook, in a few minutes.