Triggers API reference
Schedule and webhook trigger endpoints: the bodies for both kinds, secret rotation, the firings log and the signed public delivery endpoint.
Last updated:
Triggers start workflow runs without you in the loop: on a cron schedule, or when an outside system delivers a signed webhook. Everything below except the delivery endpoint itself takes an API key as a bearer token; the delivery endpoint is the single public route in the API and authenticates by HMAC instead.
/v1/workflows/:id/triggers
Creates a trigger on the workflow; the body's `kind` decides which of the two shapes below applies.
Schedule body
| Name | Type | Description |
|---|---|---|
kind
required
|
string | `schedule`. |
name
|
string | A label for the trigger. |
cron
required
|
string | A cron expression; the minimum interval between occurrences is 5 minutes. |
timezone
required
|
string | An IANA zone such as `Europe/Berlin` — never a UTC offset. Wall-clock time is meant literally: on the clocks-back day a daily job fires once, and a job in the spring-forward gap fires when the gap ends. |
catchUp
|
string | `skip` or `run_once`. However long the platform was down, one decision: drop the missed occurrences, or fire a single catch-up run. |
misfireGraceSeconds
|
integer |
60 to 21600 — how late an occurrence may still fire before it counts as missed.
Default: 300
|
overlap
|
string | `skip` or `allow`: what happens when the previous run is still going. |
inputs
|
object | Inputs handed to each run. |
session
|
object | How the session for each run is created: `{poolId, profileId, locale, timezone, ttlSeconds}`. |
version
|
integer | Pins the trigger's runs to one workflow version. |
Webhook body
| Name | Type | Description |
|---|---|---|
kind
required
|
string | `webhook`. |
name
|
string | A label for the trigger. |
inputs
|
object | Inputs for the runs this trigger starts. |
session
|
object | Session settings for each run, same shape as on a schedule trigger. |
A secret shown once
Creating a webhook trigger answers 201 with the trigger and a secret beginning whs_ — returned exactly once, at creation. Store it then; afterwards the trigger only reports hasSecret, and the way back is rotation, not read-back.
/v1/triggers
Lists triggers, narrowed with a `workflowId` query parameter.
/v1/triggers/:id
Reads one trigger, including `nextFireAt` for schedules.
/v1/triggers/:id
Updates a trigger, including flipping `status` between `active` and `paused`.
/v1/triggers/:id
Deletes the trigger.
/v1/triggers/:id/secret
Rotates the webhook secret and returns the new one.
Rotation body
| Name | Type | Description |
|---|---|---|
graceSeconds
|
integer | 0 to 604800 — how long the previous secret is still honoured; the response names the cut-off as `previousSecretUntil`. |
/v1/triggers/:id/firings
Lists every occasion the trigger came due — including the ones where nothing ran.
Why nothing ran last night
A firing's outcome is started, skipped or refused. Skips and refusals are recorded on purpose: this log answers the question a silent scheduler cannot — an occurrence suppressed by overlap: skip, one missed beyond the misfire grace, or one refused outright each leave a row here saying so.
/v1/trigger-hooks/:id
Receives a signed delivery from the outside — the only endpoint in the API that takes no API key.
Delivery headers
| Name | Type | Description |
|---|---|---|
x-browserberg-delivery
required
|
header | Your delivery id, up to 200 characters — the dedup key: a repeated id does not start a second run. |
x-browserberg-timestamp
required
|
header | Unix seconds at signing time; deliveries outside a ±300 second window are refused. |
x-browserberg-signature
required
|
header | Lowercase hex HMAC-SHA256, optionally prefixed `v1=`. |
How a delivery is verified
The signature is computed over deliveryId.timestamp.rawBody — the raw bytes you send, never a re-serialisation — and the delivery id sits under the signature, so a captured delivery cannot be replayed under fresh ids. Content-Type must be application/json, and the endpoint accepts at most 60 deliveries per minute per IP. An unknown trigger, a bad signature and an undecryptable secret all produce one identical 401, so a probe learns nothing. A delivery that starts a run answers 202 with the firing; one that is skipped or refused answers 200.
A nightly schedule in one script
Creates a workflow, attaches a Berlin-time schedule and reads the trigger back before cleaning up.
Requires: api-key
BASE="https://browserberg.com"
AUTH="Authorization: Bearer $BROWSERBERG_API_KEY"
JSON="Content-Type: application/json"
WFID=$(curl -s -X POST "$BASE/v1/workflows" -H "$AUTH" -H "$JSON" -d '{
"title": "Nightly example check",
"blocks": [ { "blockType": "navigation", "label": "Open_page", "url": "https://example.com" } ]
}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["workflow"]["workflowId"])')
# A schedule is a cron expression plus an IANA zone -- never a UTC offset.
# Wall-clock time is meant literally, including on the DST switch days.
TRIGGER=$(curl -s -X POST "$BASE/v1/workflows/$WFID/triggers" -H "$AUTH" -H "$JSON" -d '{
"kind": "schedule",
"name": "Nightly at 02:30 Berlin time",
"cron": "30 2 * * *",
"timezone": "Europe/Berlin",
"catchUp": "run_once",
"session": { "ttlSeconds": 900 }
}')
echo "$TRIGGER" | python3 -c 'import json,sys; t = json.load(sys.stdin)["trigger"]; print(json.dumps({
"id": t["id"], "status": t["status"], "nextFireAt": t["nextFireAt"],
"catchUp": t["catchUp"], "overlap": t["overlap"]}, indent=2))'
TID=$(echo "$TRIGGER" | python3 -c 'import json,sys; print(json.load(sys.stdin)["trigger"]["id"])')
curl -s -X DELETE "$BASE/v1/triggers/$TID" -H "$AUTH" > /dev/null