# Action Requests and Placeholders

> The REST requests COD Order Calls sends back to your store - confirm, cancel, tag and note POSTed to your action URL - how they are signed, retried and made idempotent, and the placeholders available in them.

Source: https://edesy.in/docs/order-calls/connect-your-store/action-requests-and-placeholders

When a customer presses 1 or 2, Edesy sends an action request to your store so the order is updated during the call. For a webhook-connected store, every action is a signed JSON POST to the action URL you set on the store. Your endpoint applies the change and answers with a 2xx status.

## The requests

All four actions go to your **action URL** as `POST` with a JSON body.

| Action | When | Body |
|---|---|---|
| `confirm` | Customer pressed 1 | `{"action": "confirm", "order_id", "order_name", "call_sid", "at"}` |
| `cancel` | Customer pressed 2 | `{"action": "cancel", "order_id", "order_name", "call_sid", "at"}` |
| `tag` | An [outcome action](https://edesy.in/docs/order-calls/call-rules/outcome-actions) adds a tag | `{"action": "tag", "order_id", "tag", "note", "call_sid"}` |
| `note` | An outcome action adds a note | `{"action": "note", "order_id", "note", "call_sid"}` |

Example confirm:

```json
{
  "action": "confirm",
  "order_id": "10452",
  "order_name": "#10452",
  "call_sid": "c4b1e2d0-...",
  "at": "2026-10-12T06:12:05Z"
}
```

`order_id` is the `order.id` you sent in the webhook. `at` is when the customer answered, in UTC (RFC 3339).

No action URL? Leave it empty. Outcomes are still recorded on the Orders screen and you can act on them there.

## Verify the request

Each request carries:

| Header | Value |
|---|---|
| `X-Edesy-Timestamp` | Unix seconds when we sent it |
| `X-Edesy-Signature` | `sha256=` + hex HMAC-SHA256 of `timestamp + "." + raw_body` |
| `X-Edesy-Call-Sid` | The call that produced the action |
| `X-Edesy-Idempotency-Key` | The same value on every retry of this request |

The signature uses your account's **IVR signing secret**, not the store's webhook secret. Find it, and rotate it, in IVR Studio at [ivr.edesy.in](https://ivr.edesy.in) under **API Keys > Signing secret**.

```js
import crypto from "node:crypto";

function verify(rawBody, ts, signature, secret) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
  return signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
```

## Success, failure and retries

- Any `2xx` response is success. During the call, the customer then hears the "confirmed" or "cancelled" line.
- If your endpoint times out or answers `5xx` or `429`, the customer hears a reassuring line ("we'll update your order shortly"), the order settles as `action_failed`, and the same request is retried in the background.
- An error body in the form `{"error": {"code": "...", "message": "..."}}` is shown in the order's timeline.
- Make your handler safe to run twice. Use `X-Edesy-Idempotency-Key` to ignore a request you have already applied.

The action request has a short time budget during the call, so answer quickly and do slow work afterwards.

## Placeholders

Action request templates, outcome tags and notes can use these tokens. They are filled in per order and call.

| Placeholder | What it is | Example |
|---|---|---|
| `{order.external_id}` | Your order id | `10452` |
| `{order.name}` | Order name the customer sees | `#10452` |
| `{order.number}` | Order number | `10452` |
| `{order.phone}` | Customer phone, E.164 | `+919800000000` |
| `{order.customer_name}` | Customer name | `Priya Sharma` |
| `{order.cod_amount}` | Cash to collect, in rupees | `1110` |
| `{order.cod_amount_paise}` | Cash to collect, in paise | `111000` |
| `{order.total}` | Order total, in rupees | `1310` |
| `{call.sid}` | Our call id | `c4b1e2d0-...` |
| `{call.time}` | When the customer answered, IST, readable | `12 Oct 2026 11:42 AM IST` |
| `{call.time_iso}` | When the customer answered, UTC, RFC 3339 | `2026-10-12T06:12:05Z` |
| `{job.attempts}` | Call attempt number | `2` |
| `{labels.confirmed}`, `{labels.cancelled}`, `{labels.pending}`, `{labels.unreachable}`, `{labels.no_input}` | The call rule's label for that outcome | `unreachable` |
| `{action.tag}` | The tag set on the outcome action | `unreachable` |
| `{action.note}` | The note set on the outcome action | `Not reachable after 3 calls` |

Labels are set per call rule; see [Outcome Actions](https://edesy.in/docs/order-calls/call-rules/outcome-actions).

## Editing a request

Some store integrations let you change the request behind each action (method, path, JSON body and extra headers) on a **Requests** tab, with a placeholder picker, a live preview and a test send. See [Test and Revisions](https://edesy.in/docs/order-calls/connect-your-store/test-and-revisions). For a webhook-connected store the requests are the fixed JSON bodies above, sent to your action URL; shape them however you like on your side.
