Sending forms
Forms are declarative blocks in a notification's detail.layout. The recipient
fills the form in Roundtrip; submitted values arrive in your signed webhook
event.
Send a form
curl
curl -X POST https://api.roundtrip.sh/api/v1/notifications \
-H "Authorization: Bearer ak_xxx" \
-H "Content-Type: application/json" \
-d '{
"channel": "approvals",
"push": { "title": "Refund request, order #4821" },
"content": {
"title": "Refund request, order #4821",
"description": "Tap to review and decide."
},
"detail": {
"title": "Review refund",
"layout": [
{
"type": "form",
"id": "refund-review",
"fields": [
{
"type": "select",
"name": "decision",
"label": "Decision",
"required": true,
"options": [
{ "label": "Approve full refund", "value": "full" },
{ "label": "Approve partial", "value": "partial" },
{ "label": "Deny", "value": "deny" }
]
},
{ "type": "textarea", "name": "note", "label": "Note to customer" }
],
"submit": { "label": "Submit decision", "action": "submit-refund", "style": "primary" }
}
]
},
"response": {
"mode": "required",
"behavior": "resolve",
"webhook": "https://api.example.com/roundtrip/webhooks"
},
"metadata": { "orderId": "4821" }
}'SDK
import { detail, form, selectField, textareaField } from "@roundtrip/sdk";
const refundWorkflow = roundtrip.webhooks.workflow({
name: "refund-review",
actions: {
review: ["submit-refund"] as const,
},
});
const refundForm = form({
type: "form",
id: "refund-review",
fields: [
selectField(
"decision",
[
{ label: "Approve full refund", value: "full" },
{ label: "Approve partial", value: "partial" },
{ label: "Deny", value: "deny" },
],
{ label: "Decision", required: true }
),
textareaField("note", { label: "Note to customer" }),
],
submit: {
label: "Submit decision",
action: refundWorkflow.action("review", "submit-refund"),
style: "primary",
},
});
await roundtrip.notify({
channel: "approvals",
push: { title: "Refund request, order #4821" },
content: {
title: "Refund request, order #4821",
description: "Tap to review and decide.",
},
detail: detail([refundForm], { title: "Review refund" }),
response: {
mode: "required",
behavior: "resolve",
webhook: "https://api.example.com/roundtrip/webhooks",
},
metadata: { orderId: "4821" },
});Receive values
Submitted forms arrive as response.submitted events. data.values is keyed by
the field name.
event.data
{
"notificationId": "ntf_2",
"kind": "form",
"action": "submit-refund",
"values": {
"decision": "partial",
"note": "Refunding shipping only."
},
"metadata": { "orderId": "4821" }
}handler
const router = roundtrip.webhooks
.workflow({
name: "refund-review",
actions: {
review: ["submit-refund"] as const,
},
})
.onAction("review", "submit-refund", async ({ data }) => {
const { decision, note } = data.values ?? {};
await applyRefund(data.metadata.orderId, decision, note);
});Verify the raw body first
Use webhooks.unwrap with the raw request body and Roundtrip-Signature
header before trusting event.data.
Form fields
text / textarea / emailstringoptionalSingle-line, multi-line, and email-shaped inputs.
numbernumberoptionalNumeric input.
select / multiselectoptionsoptionalChoice inputs with explicit { label, value } options.
switchbooleanoptionalA boolean toggle.