Skip to content

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.

POST /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.

GET /v1/triggers

Lists triggers, narrowed with a `workflowId` query parameter.

GET /v1/triggers/:id

Reads one trigger, including `nextFireAt` for schedules.

PATCH /v1/triggers/:id

Updates a trigger, including flipping `status` between `active` and `paused`.

DELETE /v1/triggers/:id

Deletes the trigger.

POST /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`.
GET /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.

POST /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

Further

  • Schedule triggers DST days and downtime, worked through
  • Webhook triggers Sign a delivery end to end
  • Workflows reference The runs a trigger starts