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 03 · What a job does

HTTP request and response

The HTTP Request action calls another system: method, URL, headers, body, timeout, retries and the status codes it accepts, and what it leaves for later actions. The HTTP Response action answers a synchronous web event, with a success or a structured error.

Who this is for. Integrations engineers. Afterwards you can send a request that survives a quote in the data, handle a status you expect rather than failing on it, and answer a caller with JSON.

HTTP Request

FieldTakes
Method, URLThe URL is template code, so it can carry an id or a site data value
HTTP request headersTemplate code, one Name: value per line. Content-Type: application/json is added when no content type is given
Body Template File or Body Template CodeThe body. With neither, an empty body is sent
TimeoutSeconds, from 10 to 120; 60 when empty
Retries0 to 10; none when empty. Each retry waits twice as long as the one before, starting at 0.3 seconds: 0.3, 0.6, 1.2, 2.4 and so on, about five minutes of waiting in all for ten, inside the running job
Accepted status codesA comma-separated list with * as a wildcard, 2** when empty

The body, built as a value so that any content stays valid JSON, and the headers with the key from site data:

Template code
{* Body Template Code *}
{$answers = $event->getRecord()->getData()}
{$payload = ['email' => $answers['email'], 'source' => 'website-contact-form']}
{$payload|json_encode}

{* HTTP request headers *}
Authorization: Bearer {$siteData->get('crm-api-key')}
Accept: application/json
{# Body Template Code #}
{% set answers = event.getRecord().getData() %}
{{ {'email': answers.email, 'source': 'website-contact-form'}|json_encode }}

{# HTTP request headers #}
Authorization: Bearer {{ siteData.get('crm-api-key') }}
Accept: application/json

What it leaves for later actions

KeyHolds
responseCodeThe status code
responseHeadersThe response headers, a list of values per name
responseDataA JSON answer, parsed; an XML answer as an XML object
responseBodyAny other answer as text, up to 100,000 characters. A longer one is kept in a temporary file, which a Create File action can save

The run log shows the method, URL, request headers, the first 5,000 characters of the body sent, the response code and content, the retries and the duration: enough to reproduce the call by hand.

The default that surprises people

Any status outside the accepted codes fails the action, and a failed action stops the run. A CRM that answers 404 for "no such contact" stops the job there, though 404 was an answer you wanted to handle. Accept it (2**,404) and branch on the code in a condition:

Template code
{* condition: contact-not-found *}
{if $runData->get('find-contact')->responseCode == 404}{$result->setMatching(true)}{/if}
{# condition: contact-not-found #}
{% if runData.get('find-contact').responseCode == 404 %}{% do result.setMatching(true) %}{% endif %}

Retries apply to failures only: a refused status or a timeout. A request that is not safe to send twice, one that creates something on the other side, should either keep retries at zero or carry an id the other system removes duplicates by. A 409 "already exists" from such a system usually means an earlier attempt got through: accept it as success. The longer retry policy, for an outage that lasts an hour, is on Retry, backoff and dead letters; the credentials themselves on Credentials for a third party.

HTTP Response

The reply to a synchronous web event. Status Code, HTTP response headers and Body are all template code; Stop execution ends the run as a success once the reply is set. The caller receives the last HTTP Response that succeeded. The status code must render to exactly three digits from 100 to 599, so keep its template on one line: a stray line break fails the action with Invalid status code. Set Content-Type: application/json yourself when the body is JSON.

A success and a structured error, as two branches of a condition on the request's body:

Template code
{* branch has-name: status 201, body *}
{$payload = ['id' => $runData->get('create')->id]}
{$payload|json_encode}

{* else branch: status 422, body *}
{$payload = ['error' => 'name_required', 'message' => 'Send a JSON body with a non-empty name.']}
{$payload|json_encode}
{# branch has-name: status 201, body #}
{{ {'id': runData.get('create').id}|json_encode }}

{# else branch: status 422, body #}
{{ {'error': 'name_required', 'message': 'Send a JSON body with a non-empty name.'}|json_encode }}

If a synchronous job ends with no HTTP Response, the caller receives the run itself as JSON, with HTTP 200 when it succeeded and 500 otherwise. In an asynchronous job an HTTP Response has nobody to answer: the caller was answered when the run was queued. The complete endpoint, with authentication and the errors a caller sees, is on Custom endpoints: Shaping the response.

Go deeper

Was this page helpful?

Your answer goes to the docs team.