App platform
Automation triggers and actions
Apps can add their own triggers and actions to Kweko automations. A trigger starts automations when something happens in your app; an action is a step that calls your backend and hands its outputs to the next steps.
How it works
| Piece | What it does |
|---|---|
| Trigger | Your backend calls POST /v1/apps/triggers/{key} for a lead, contact or company. Kweko starts every active automation listening to app:<your app slug>:<key> for that record. |
| Action | A step in an automation. When a run reaches it, Kweko sends a signed POST to your backend; you answer with outputs. |
| Fields and outputs | Typed values. Conditions and texts read a trigger's fields as event.data.<field> and an action's outputs as run.app.<slug>.<action>.<output>. |
Both need the automations:extend scope, plus the read scope of the record type they work on (leads:read, contacts:read or companies:read). Workspaces see your triggers and actions in pipeline settings, grouped under Apps with your logo. Adding the scope to a published app asks every workspace to approve it again.
Automations stay bound to a pipeline stage: a trigger starts an automation only for leads that are in its stage, exactly like Kweko's own triggers.
Declare them in the manifest
Edit them in the developer console (Automation tab) or send them with your manifest:
{
"scopes": ["leads:read", "automations:extend"],
"base_url": "https://shop.example.com",
"automation": {
"triggers": [
{
"key": "order_paid",
"entity": "lead",
"title": { "en": "Order paid", "ru": "Заказ оплачен", "uz": "Buyurtma toʻlandi" },
"description": { "en": "The shop received the payment." },
"fields": [
{ "key": "order_id", "type": "string", "title": { "en": "Order number" } },
{ "key": "amount", "type": "number", "title": { "en": "Amount" } },
{ "key": "method", "type": "enum", "values": ["cash", "card", "transfer"], "title": { "en": "Payment method" } }
]
}
],
"actions": [
{
"key": "check_stock",
"entity": "lead",
"url": "/kweko/automation/check-stock",
"title": { "en": "Check stock" },
"params": {
"type": "object",
"required": ["sku"],
"properties": {
"sku": { "type": "string", "title": "SKU", "maxLength": 60 },
"warehouse": { "type": "string", "title": "Warehouse", "enum": ["tas", "sam"], "enumNames": ["Tashkent", "Samarkand"] }
}
},
"outputs": [
{ "key": "in_stock", "type": "boolean", "title": { "en": "In stock" } },
{ "key": "quantity", "type": "number", "title": { "en": "Quantity" } }
]
}
]
}
}| Rule | Limit |
|---|---|
| Triggers, actions | Up to 20 of each |
key (trigger, action, field, output) | 1 to 40 lowercase letters, digits or _, starting with a letter; unique in its list |
title | Required, by language, 1 to 60 characters, no markup |
description | Optional, by language, up to 300 characters |
entity | lead, contact or company, with its read scope granted |
Trigger fields | Up to 30. type is string, number, boolean, date or enum; an enum lists 1 to 50 values |
Action params | The settings schema format, up to 30 properties. No secret fields: secrets belong in your app settings |
Action outputs | Up to 20. type is string, number, boolean or date |
Action url | A path on your base_url that starts with a single / |
Workspaces fill an action's params with Kweko's own controls, the same ones your settings page uses (pickers for pipelines, stages, members and fields included). Text params may hold templates such as {{lead.name}}.
Fire a trigger
Call it from your backend with the installation's access token:
curl -X POST https://api.kweko.uz/v1/apps/triggers/order_paid \
-H "Authorization: Bearer kwk_at_…" \
-H "Idempotency-Key: ORD-1042" \
-H "Content-Type: application/json" \
-d '{"lead_id": "ld_01j8za0b1c2d3e4f5g6h7j8k9m", "data": {"order_id": "ORD-1042", "amount": 250000, "method": "card"}}'{ "trigger": "order_paid", "runs_started": 1, "dropped": [] }- Send the record as
lead_id,contact_idorcompany_id, matching the trigger'sentity. It must exist in the installation's workspace. - A contact or company trigger runs for that record's leads, the 50 most recently updated.
datakeeps only the fields you declared; unknown keys andnullvalues are dropped and listed indropped. Each value must match its type: text up to 1000 characters, a number,trueorfalse, a date asYYYY-MM-DDor RFC 3339, or one of the enum's values.datais at most 16 KB.Idempotency-Key(oridempotency_keyin the body), 1 to 100 letters, digits,.,_,:or-: the same key for the same record starts each automation once. Use your own event or order id.202means accepted;runs_startedis how many automations started now.0is normal when none listen in the lead's stage.- Each installation may fire 5 triggers a second, in bursts of up to 60.
| Status | Code | When |
|---|---|---|
403 | app_not_installed | The installation is not active |
403 | app_suspended | Kweko disabled the app |
403 | missing_scope | The workspace has not granted automations:extend or the record's read scope |
404 | unknown_trigger | The installed version of your app has no trigger with this key |
422 | validation_failed | A bad record id, a value of the wrong type, data over 16 KB |
429 | rate_limited | Too many triggers; wait retry_after seconds |
503 | automations_unavailable | Automations are paused on Kweko's side; try again later |
Answer an action
When a run reaches your action, Kweko sends a POST to base_url + url, signed with your client secret like every other request (see verify signatures):
POST /kweko/automation/check-stock
Content-Type: application/json
Kweko-App: app_01j8za0b1c2d3e4f5g6h7j8k9m
Kweko-Timestamp: 1767225600
Kweko-Signature: t=1767225600,v1=5f0c…
Kweko-Installation-Id: ins_01j8…
Kweko-Action-Id: run_01j8…:2{
"type": "automation.action",
"action": "check_stock",
"action_id": "run_01j8…:2",
"workspace": { "id": "ws_01j8…" },
"installation_id": "ins_01j8…",
"automation": { "id": "au_01j8…", "name": "Paid orders" },
"run_id": "run_01j8…",
"entity": { "type": "lead", "id": "ld_01j8…", "name": "Order #1042", "amount": 2500, "currency": "UZS" },
"params": { "sku": "TSHIRT-RED-M", "warehouse": "tas" },
"locale": "en",
"issued_at": "2026-01-01T00:00:00Z"
}entityalways hastypeandid; the other fields come only with the record type's read scope.paramsare what the workspace filled in, with templates already resolved.action_idis the same for every retry of one step in one run. Use it to skip work you already did.
Answer within 10 seconds with JSON:
{ "outputs": { "in_stock": true, "quantity": 12 } }Every output you return must be declared and match its type; leave out the ones you don't have. Later steps read them as {{run.app.<slug>.check_stock.in_stock}}, where <slug> is your app's slug with - turned into _.
With @kweko/sdk/server:
import { verifyActionRequest } from "@kweko/sdk/server"
const req = await verifyActionRequest({ secret: process.env.KWEKO_CLIENT_SECRET!, headers: request.headers, body: raw })
if (!req) return new Response("bad signature", { status: 401 })
return Response.json({ outputs: { in_stock: true, quantity: 12 } })Retries and failures
| What happens | The step |
|---|---|
Your answer is 2xx with valid outputs | Succeeds; outputs are stored on the run |
Timeout, connection error, 429 or 5xx | Is retried after 10 seconds, 1 minute and 5 minutes, then fails |
Any other 4xx | Fails at once (app_error) |
| Invalid JSON or outputs that don't match the manifest | Fails (app_bad_response) |
| The app is uninstalled, suspended, lost a scope or no longer has the action | Fails with app_not_installed, app_suspended, app_missing_scope or app_unknown_action |
Each failure shows in the automation's run log with the reason. The editor flags automations whose app is missing or suspended. Test runs (the editor's Test button) never call your backend.
Events
Separately from automation, an app can receive webhook events (lead.won, task.created and the rest of the event catalog) for every installation: pick them in the console under Extension points, or list them as events in the manifest. They go to your webhook URL, signed with your client secret, and each group needs its read scope.
The example app
packages/example-app in the Kweko repository declares the order_paid trigger and the check_stock action above. Its /demo/order-paid route fires the trigger and /kweko/automation/check-stock answers the action.