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.

Send a notification

A notification is one authenticated POST /api/v1/notifications with a channel and a push.title. No SDK required:

curl
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:

200 OK
{
  "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.

pushobjectrequired

Lock-screen copy and delivery behavior: title, body, priority, sound, badge, and enabled.

contentobjectoptional

Main in-app card: title, description, status, sections, and actions.

detailobjectoptional

A richer screen opened from the card. Holds larger blocks, forms, JSON, timelines, files, and longer text.

responseobjectoptional

Set 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 stateoptional

Replace the main content or a named slot. Use this for current status, progress, and decisions.

appendhistoryoptional

Add chronological history. Use this for incident timelines, job logs, audit entries, and progress notes.

pushfollow-up deliveryoptional

Send another lock-screen notification only when you explicitly call /push or include push copy in an append.

SDK
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

Channelstringoptional

A named stream in a workspace. You publish to channels; people subscribe to them. The target channel determines who is notified.

Notificationpush + contentoptional

The durable object Roundtrip renders in feeds and detail screens. It has a current snapshot, named slots, append history, responses, and deliveries.

Webhook eventsigned eventoptional

A signed POST to response.webhook with the action id, form values, echoed metadata, notificationId, and target slot/revision if relevant.

API keyak_...optional

A workspace-scoped bearer token. New keys include notifications:write, notifications:read, and channels:read scopes.

Next