Introduction
Push notifications with durable content and replies. Roundtrip lets apps and agents send notifications to people's phones, then keep the in-app UI updated as the underlying workflow changes. Users respond with a tap, form, or message, and every response is delivered to your backend as a signed webhook event.
A notification has two layers:
push: interruptive delivery copy. This is what appears on the lock screen.content: durable in-app UI. This is what opens in Roundtrip and can be replaced, extended with named slots, or appended to over time.
Quickstart
Send a notification with curl, then update it.
Notification workflows
SDK examples for incidents, decisions, progress, and webhooks.
Send a notification
A notification is one authenticated POST /api/v1/notifications with a channel
and a push.title. No SDK required:
curl -X POST https://api.roundtrip.sh/api/v1/notifications \
-H "Authorization: Bearer ak_xxx" \
-H "Content-Type: application/json" \
-d '{
"channel": "deploys",
"push": { "title": "Build #1283 passed" }
}'The response returns a notification id you can read, update, append to, or use to correlate webhook events:
{
"ok": true,
"notifications": [{ "channel": "deploys", "id": "ntf_1", "deviceCount": 3 }],
"errors": []
}Add durable UI
The request body grows from there. push is still only the delivery copy;
content, detail, and response are the durable UI and response contract.
pushobjectrequiredLock-screen copy and delivery behavior: title, body, priority,
sound, badge, and enabled.
contentobjectoptionalMain in-app card: title, description, status, sections, and actions.
detailobjectoptionalA richer screen opened from the card. Holds larger blocks, forms, JSON, timelines, files, and longer text.
responseobjectoptionalSet mode, behavior, and webhook to receive tapped actions and form
submissions as signed events.
Update over time
The low-level primitive is intentionally small: create one notification, then mutate its durable content explicitly.
replacecurrent stateoptionalReplace the main content or a named slot. Use this for current status, progress, and decisions.
appendhistoryoptionalAdd chronological history. Use this for incident timelines, job logs, audit entries, and progress notes.
pushfollow-up deliveryoptionalSend another lock-screen notification only when you explicitly call /push
or include push copy in an append.
const notification = await roundtrip.notify({
channel: "on-call",
push: { title: "Checkout latency is high", priority: "high" },
content: {
title: "Checkout latency",
description: "Investigating elevated p95 latency.",
status: "warning",
},
});
await notification.content.append({
content: {
title: "Database checked",
description: "No saturation found.",
status: "success",
},
});
await notification.content.replace("decision", {
content: {
title: "Approve rollback?",
description: "Rollback candidate is ready.",
status: "warning",
actions: [{ id: "rollback", label: "Rollback", style: "destructive" }],
},
response: { mode: "required", behavior: "resolve" },
});
await notification.push({
title: "Rollback decision needed",
body: "A rollback is ready for approval.",
priority: "high",
});Concepts
ChannelstringoptionalA named stream in a workspace. You publish to channels; people subscribe to them. The target channel determines who is notified.
Notificationpush + contentoptionalThe durable object Roundtrip renders in feeds and detail screens. It has a current snapshot, named slots, append history, responses, and deliveries.
Webhook eventsigned eventoptionalA signed POST to response.webhook with the action id, form values,
echoed metadata, notificationId, and target slot/revision if relevant.
API keyak_...optionalA workspace-scoped bearer token. New keys include notifications:write,
notifications:read, and channels:read scopes.