Actions
PreviewLet a call do things in your systems, such as confirming an order or changing an address, by RowCall's signed request or your own HTTP request.
An action is something only your systems can do: look up an order, change a delivery address, send a payment link. During a call, when the flow or the shopper needs one, RowCall takes the action and waits for the reply while the shopper stays on the line.
The AI never acts directly. It asks for an action; RowCall checks the request; your systems check it again against your own data and decide.
Define what the call may do
Actions are your org's own. RowCall suggests presets you can use as they are or change: get_order, confirm_order, cancel_order, update_delivery_address, get_checkout, send_payment_link, restore_checkout_link, get_delivery_slots, book_delivery_slot, create_exchange and handoff. You can also define any action of your own, such as check_warranty.
Manage them in Developers → Actions, or with GET /v1/actions and PUT /v1/actions/{name} (actions scope). An action is mostly words for the AI and limits for RowCall:
await rowcall.actions.save('confirm_order', {
description: 'Marks the order as confirmed by the shopper, so it ships.',
kind: 'write',
parameters: { order_id: { type: 'string', description: 'The order number.' } },
sample: { confirmed: true },
transport: 'rowcall',
requiresConfirmation: true,
})descriptionandparametersare what the AI reads to know when to use the action and what to pass. Write them plainly. Parameters can't change while a published flow uses the action.kindisread(looks something up) orwrite(changes something).sampleis what a success looks like. Simulations use it, and so do calls while the action isn't connected.requiresConfirmationmakes RowCall send the action only after the shopper agreed at a confirm step in this call.timeoutMs(500 to 5000; 1500 for reads and 2500 for writes by default) andmaxPerCall(default 2) keep the call moving.
A new action of your own needs description, kind and parameters; a preset starts from its own definition, so you send only what you change. Every field is in the reference. DELETE /v1/actions/{name} removes one of yours, or puts a changed preset back.
Allow it in a flow
Defining an action doesn't let any flow use it. Each flow lists its allowed actions in the flow builder, and its action steps can only call those. A request for an action that isn't on the list is refused before it reaches you. When an argument is missing but the run has an input of the same name, such as order_id, RowCall fills it from the run.
Connect it
An action is taken one of three ways, its transport:
rowcall: RowCall sends a signedPOSTto your action URL for the call's mode (below).http: RowCall sends your own HTTP request, built from a template, straight to an API you already have.none: not connected yet; the call gets the action's sample reply.
RowCall's request (rowcall)
Set one action URL for live and one for test in Developers → Actions (HTTPS only, never taken from run inputs). Each has its own signing secret (whsec_…, shown once); after a roll, the old secret signs too for 24 hours. Until a mode has a URL, its calls get the sample reply.
RowCall posts the action request: which action, the arguments the AI filled, the run's metadata, whether the shopper confirmed, and a deadline. It's signed with the same RowCall-Signature as webhooks, checked with verifyActionRequest from the SDK, and carries an Idempotency-Key (<callId>:<actionId>) that stays the same if RowCall sends it again, so do the work once per key.
import { verifyActionRequest } from '@rowcall/sdk'
app.post('/rowcall/actions', express.raw({ type: 'application/json' }), async (req, res) => {
const body = req.body.toString()
if (
!verifyActionRequest({
body,
signature: req.get('RowCall-Signature') ?? '',
secret: process.env.ROWCALL_ACTION_SECRET,
})
) {
return res.sendStatus(401)
}
const request = JSON.parse(body)
if (request.action === 'confirm_order') {
const order = await orders.find(request.args.order_id)
if (order.shippedAt)
return res.json({ ok: false, errorCode: 'ALREADY_SHIPPED', say: 'This order has already shipped.' })
await orders.confirm(order.id)
return res.json({ ok: true, result: { confirmed: true } })
}
res.status(404).end()
})How you answer decides what the agent does next:
200 { "ok": true, "result": { … } }: success. The agent can useresultin what it says.200 { "ok": false, "errorCode": "…", "say": "…" }: refused. The agent explains, usingsay.202 { "pending": true }: it takes longer. The agent says it's on it; you finish it later (below).- Anything else, or no reply by the deadline: timeout. A
readaction is tried once more, with the sameIdempotency-Key, when two tries fit within 6 seconds.
The flow's action step branches on the result: success, refused, pending or timeout.
Finishing a pending action
When you answered 202, finish the action with POST /v1/runs/{runId}/actions/{actionId}/result, sending ok: true with a result, or ok: false with an errorCode and say. The call may be over by then; the result is still logged and announced. An action that already has its result is refused as CONFLICT.
Your own HTTP request (http)
Point an action straight at an API you already have, with transport: "http" and an http template:
{
"transport": "http",
"http": {
"method": "GET",
"url": "https://crm.example.in/warranty/{serial}",
"body": null,
"headers": { "Authorization": "Bearer …" },
"successField": "data.isCovered",
"resultFields": ["data.isCovered", "data.expiresOn"],
"sayField": "message"
}
}{parameter}placeholders in the URL and JSON body are filled from the action's arguments.headers(up to 10) are stored encrypted and never shown again; leave them out to keep the saved ones.- A
2xxis success, unlesssuccessFieldnames a reply field that isn'ttrue(or"ok","success","yes"): then it's refused. A4xxis refused. A5xx, a redirect or no reply is a timeout. resultFieldspicks what the AI gets from the reply (dot paths); empty means all of it.sayFieldis what to tell the shopper on a refusal.
RowCall's own checks
Before taking an action, RowCall refuses it itself with LIMIT_REACHED when maxPerCall is used up, and with NOT_CONFIRMED when it needs a confirmation the shopper hasn't given. Every action is logged with its arguments, result and timing, on the call page and in the run's actions, and announced as action.completed or action.failed.
In test mode, rowcall actions go to your test action URL; http actions never run and get their sample reply instead, so test runs never touch a live system.
Keep it quick, and check again
The shopper is waiting on the phone. Keep timeoutMs short and host your endpoint in India, close to RowCall's servers in Bengaluru. RowCall's checks are a first gate: your system must still check that the order belongs to this shopper and that the change is allowed right now.
Test your actions without a phone call.