Guide 09 · Developer docs

Automation, jobs and integration

A job is a trigger, conditions and an ordered list of actions, set up under Customization › Orchestration in the admin; jobs were called hooks before the module was renamed, and the API's routes keep that name. What starts a job, what it can do, branches and loops, email, scheduled work, integrations with other systems, and every event and action.

L2–L3
Unit 02 · What starts a job

Web events and schedules

The two triggers that are not about a record: a web event gives a job a URL for another system to call, and a schedule runs it on the clock. What each run receives, how schedules are set and in which timezone, and what happens when a run is still going as the next one comes due.

Who this is for. Integrations engineers taking calls from other systems, and anyone running work at night or on the hour. Afterwards you can give a job a URL or a timetable and know how overlapping runs behave.

Web events

A Web Event Trigger gives the job an address, /cdna-api/webhook/<slug>, on every hostname of the site, for the methods you tick: GET, POST, PUT, DELETE or OPTIONS. Anything after the slug arrives as path parameters. The run's event carries the request:

On eventReturns
getMethod()The method, such as post
getQueryParameters(), getQueryParameter(name)The query string
getPathParameters()The path segments after the slug
getHeaders(), getHeader(name)The request headers
getBody(), getContentType()The raw body and its type
getBodyData()A JSON or form body, parsed into a read-only collection; toArray() for a plain array

An asynchronous job queues the run and the caller gets an immediate answer that it was accepted. A job with Synchronous execution on runs while the caller waits and answers with its last successful HTTP Response action. With Authentication required ticked, only requests authenticated as a backend user (an OAuth access token, or an API key) are let through; anything else gets a 403. An unpublished job, or a job limited to another site, answers 404.

That is all a trigger needs. Building an endpoint on it, from the synchronous flag to the response shape and securing an open URL, is the subject of Custom endpoints with synchronous jobs, in the APIs guide.

Schedules

A schedule is set on the job itself, and needs the job's Run on site to be set: a job for every site cannot be scheduled, because the scheduler would not know which site to run it in. Two kinds, which can be combined:

  • Recurring execution: the five fields of a cron expression, Minute, Hour, Day, Month and Day of week, with an optional Paused until before which it does not start and Recurring until after which it stops. Minute 0, hour 2 and the rest * is 02:00 every day; minute */15, hour 8-18, day of week 1-5 is every fifteen minutes in office hours on weekdays.
  • Scheduled executions: one-off dates and times, up to 100 on a job.

Times are in the site's timezone (the Globals timezone setting), not the editor's. The run's event.getTask() is the scheduler task that came due; there is no record. How the scheduler decides a task is due, and what the platform's own tasks are, is on Scheduler tasks and the job queue.

Missed runs

The scheduler checks every task once a minute. A recurring execution is due only in the exact minute its fields name: if the scheduler was not running at 02:00, the 02:00 run does not happen later, and the next one is tomorrow's. A one-off execution that was missed runs at the first check after its time.

When the previous run has not finished

The scheduler does not look at the previous run. Each time the schedule comes due a new run is queued, so a run that takes twenty minutes on a fifteen-minute schedule overlaps the next. Three defences, in order of importance:

  1. Keep a run well inside its interval, by handling a chunk per run (Watermarks and chunking).
  2. Make the selection safe to repeat. Select the work that is still to do (a missing external id, a status, a watermark), so a second run finds nothing left, rather than work by position, which two runs both take.
  3. Skip a run while another is recent. A marker in site data, set at the start and cleared at the end, with an age limit so a run that died does not block the job for ever. The first action is a condition whose branch holds a Stop with status Skipped:
Template code
{* condition: another-run-is-recent *}
{$since = $siteData->get('product-export-running')}
{if $since && date_create($since) > date_create('-2 hours')}{$result->setMatching(true)}{/if}

{* second action, a Render Template: set the marker *}
{$siteData->set('product-export-running', date('c'))}

{* last action, a Render Template: clear it *}
{$siteData->set('product-export-running', '')}
{# condition: another-run-is-recent #}
{% set since = siteData.get('product-export-running') %}
{% if since and date(since) > date('-2 hours') %}{% do result.setMatching(true) %}{% endif %}

{# second action, a Render Template: set the marker #}
{% do siteData.set('product-export-running', 'now'|date('c')) %}

{# last action, a Render Template: clear it #}
{% do siteData.set('product-export-running', '') %}

The marker is stored with its offset (format('c')), so it reads back as the same moment whatever the timezone. It is not a lock: two runs that start in the same second can both pass the condition. It is enough for the real case, a slow run still going when the next is due, and the age limit is longer than any healthy run.

Go deeper

Was this page helpful?

Your answer goes to the docs team.