Guides
Answer workflow calls
Workflows answer account questions for signed-in patients, such as where an order is, by calling your own systems. Here's what those calls look like and how to answer them.
What a workflow sends
Your team builds workflows in the dashboard under Workflows, from a template or from scratch. A Call API step calls one of your systems through a connection: an address plus its credentials (bearer, basic, an API key header or a key in the body). The credentials live on the connection, never in the workflow.
- Workflows run only for signed-in patients, and never for a health question or an emergency.
- The only patient details a workflow can send are
{{patient.email}}and{{patient.externalId}}(the ID you enrolled the patient with), plus{{run.id}}. - A call that uses anything the patient typed must check the record is theirs, by the email or patient ID in your response. A record that isn't theirs reads as not found.
The request
| Rule | Detail |
|---|---|
| Address | HTTPS on the public internet. Private, loopback and link-local addresses are refused on every call. |
| Attempts | One, with no retry while the patient waits, and no redirects. |
| Time | NezerCare waits at most 5 seconds, or less if the connection says so. |
| Size | Your response is capped at 1 MB. |
If a call fails, the patient is told it couldn't be checked and offered your team. Nothing is guessed.
Check the signature
When the environment has a widget signing secret (the one you sign widget sign-ins with), every call carries x-nezer-timestamp (seconds since the epoch) and x-nezer-signature. It uses the same HMAC as webhooks, but also covers the method and path, because a GET has no body:
<x-nezer-timestamp>.<METHOD> <path and query>\n<raw body>
x-nezer-signature = "v1=" + lowercase hex
HMAC-SHA256(widget signing secret, signed text)import { createHmac, timingSafeEqual } from "node:crypto";
export function isFromNezer(req, rawBody, secret) {
const timestamp = req.get("x-nezer-timestamp") ?? "";
const signature = req.get("x-nezer-signature") ?? "";
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!/^\d+$/.test(timestamp) || age > 300) return false;
const signed = `${timestamp}.${req.method} ${req.originalUrl}\n${rawBody}`;
const hmac = createHmac("sha256", secret).update(signed).digest("hex");
const expected = Buffer.from(`v1=${hmac}`);
const given = Buffer.from(signature);
return given.length === expected.length && timingSafeEqual(given, expected);
}req.originalUrl must be the path NezerCare requested. Behind a proxy that rewrites paths, use the path before the rewrite.
Order lookup
The Order status and tracking template asks your order system for the signed-in patient's orders and shows the latest as cards. The patient types nothing:
POST <connection address>/orders/lookup
content-type: application/json
x-nezer-timestamp: 1790000000
x-nezer-signature: v1=5f1c…
{
"version": 1,
"type": "order_lookup.requested",
"requestId": "<the run's id>",
"patient": { "externalId": "test-patient-1" }
}patient.externalId is the ID you enrolled the patient with. A patient enrolled without one is sent an empty string; answer with no orders.
The answer
Answer 200 with an orders array, empty when there are none:
{
"orders": [
{
"id": "TEST-1003",
"status": "shipped",
"statusLabel": "On its way",
"carrier": "UPS",
"trackingUrl": "https://www.ups.com/track?tracknum=1Z0000000000000000",
"estimatedDelivery": "2026-10-02",
"placedAt": "2026-09-25T15:04:00Z",
"items": [{ "name": "Test Product A", "quantity": 1 }]
}
]
}| Field | Rule |
|---|---|
orders | Required, at most 50. Use [] when there are none or the patient is unknown. |
id | Required string, 1–64 characters. A number is refused. |
status | Required: processing, shipped, delivered, delayed or cancelled. |
statusLabel | Optional, up to 80 characters, shown instead of the widget's own word. |
carrier | Optional, up to 60 characters. |
trackingUrl | Optional. Shown only when it's HTTPS without a user name or password. |
estimatedDelivery, placedAt | Optional: YYYY-MM-DD, or an ISO 8601 date-time with a time zone. |
items | Optional, at most 20, each with a name (1–120 characters) and a whole-number quantity from 1 to 999. |
The whole answer is checked strictly
A wrong type, an unknown status or a malformed date makes the whole answer invalid, and the patient sees the step's error reply instead. Fields not listed are ignored, and null means not given. The widget shows at most three orders: latest first when every order has placedAt, otherwise in your order.
Test it
In the workflow builder's Test stage, enter a sample patient's ID in your system (for example test-patient-1) and run it. The result shows the exact request and response and, for an answer that breaks the contract, why. Test runs call your real connection but never message a patient. A passing test within 24 hours is needed to switch a workflow on.
The endpoints behind the builder are in the workflow setup reference. They take a staff sign-in, not an API key.