Workflows, schedules and webhooks
A workflow is work your app does on its own, on the server: tell an instructor about a new booking, send a digest every morning, record a payment when the provider calls back. Runs are queued, retried and logged, so you can see what happened and replay what failed.
When to use it
| Trigger | Example | In the contract |
|---|---|---|
| A row is created or changes | Notify the instructor of a new booking | { "entity": "booking", "event": "created" } |
| A field changes to a value | Free a mat when a booking is cancelled | "when": { "field": "status", "equals": "cancelled" } |
| A schedule | A digest every morning | { "schedule": "daily 07:00", "tz": "Europe/Lisbon" } |
| Another service calls | A payment callback creates a row | { "webhook": "payment-received" } |
| Your screen calls it | Fetch a weather forecast | { "call": true }, see server functions |
Workflows run after a write. To stop a write, use a business rule.
Step by step
- Pick the trigger (
on). - Pick the actions (
do):notify,email,integration,record(webhooks only) orcustomcode. - Use
{{field}}placeholders to fill text from the row. - Apply. The reply lists the workflows it armed; verify checks they are armed.
- Test it now with Backend, Workflows, Run now, or the
runtool, instead of waiting for the trigger.
Code
After a change, with a custom step
{
"id": "count-booking",
"title": "Count the mat and tell instructors",
"on": { "entity": "booking", "event": "created" },
"do": [{ "action": "custom", "code": "const s = await ctx.db.get('session', String(row.session_id));\nif (!s) return;\nconst booked = Number(s.booked ?? 0) + 1;\nawait ctx.db.update('session', String(s.id), { booked });\nctx.notify({ title: 'New booking', body: `${String(s.title)}: ${booked} of ${Number(s.capacity)} mats booked`, role: 'instructor' });" }]
}A custom step gets ctx, row and event. ctx.db.get / list / create / update, ctx.notify, ctx.email, ctx.vars, ctx.log, ctx.now and ctx.aiare available. It is type-checked on apply; a step that doesn't check is dropped and the report says why.
When a field changes
{ "id": "free-mat-on-cancel", "title": "Free the mat",
"on": { "entity": "booking", "event": "updated" },
"when": { "field": "status", "equals": "cancelled" },
"do": [{ "action": "custom", "code": "..." }] }On a schedule
{ "id": "morning-digest", "title": "Morning bookings digest",
"on": { "schedule": "daily 07:00", "entity": "booking", "tz": "Europe/Lisbon" },
"do": [{ "action": "notify", "role": "instructor", "title": "Good morning",
"body": "{{count}} bookings in the last 24 hours.\n{{by:status}}" }] }Schedules: daily 08:00, hourly, every 30m, weekly mon 09:00, monthly 09:00, monthly 15 09:00. The digest covers the window since the last run. Placeholders: {{count}}, {{items}}, {{period}}, {{summary}}, {{total:amount}}, {{by:status}}, {{total_by:amount:stage}}.
When another service calls (webhook)
{ "id": "payment-received", "title": "Payment provider callback",
"on": { "webhook": "payment-received" },
"do": [{ "action": "record", "entity": "payment_event" }] }Appbricx hosts it at POST /hooks/<project-id>/payment-received. Callers sign the raw body with HMAC-SHA256 and send the hex digest in x-appbricx-signature. Copy the signing secret from Backend, API.
BODY='{"invoice_number":"INV-1001","amount":4200}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | sed 's/^.* //')
curl -sS -X POST "https://<your-app-host>/hooks/$PROJECT_ID/payment-received" \
-H "content-type: application/json" -H "x-appbricx-signature: $SIG" -d "$BODY"Add x-appbricx-idempotency-key and a repeat within 24 hours returns without running again.
See runs and fix failures
- Backend, Logs lists runs with their log lines and notifications. Agents use
logsortail. - A failed run is retried. When it runs out of retries it lands in Backend, Workflows, Failed jobs, where Replay runs it again with the same input (agents:
runwithdead_lettersandreplay). - Run now runs a workflow on dev or prod with a JSON input you type, and shows the result.
Gotchas
- Up to 10 workflows per contract. A present
workflowslist replaces the current ones; omitting it keeps them. - Schedules run in UTC unless you set
tz. - An
integrationstep is skipped until the owner connects that integration. - Notify one person with
"to": "<ref field>"instead of a whole role. - Custom steps have no network. For third-party APIs, use a server function or an integration.
- Workflows write as the platform, past access rules, and don't trigger business rules. Keep their writes narrow.