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 07 · Jobs and other systems

The integration pattern catalogue

Find your case: the usual direction, cadence and pitfalls of a CRM, ERP, fulfilment, marketing automation, analytics or chat integration built from jobs, and where an integration's credentials live.

Who this is for. Integration engineers connecting a Core dna site to another system with jobs. Find your kind of system in the catalogue, then read where its credentials go. The rules every integration follows have pages of their own: retries and dead letters, field ownership, and a complete worked integration.

The pattern catalogue

The usual shape of each, as a starting point rather than a rule. "Push" is the site sending on an event; "pull" is a scheduled job fetching.

SystemUsual direction and cadenceWhat to watch
CRM (contacts, leads, accounts)Push a lead when a form submission is completed and a contact when a person changes, within seconds. Pull the fields the CRM owns (owner, lifecycle stage) nightlyMatch on the CRM's id stored in externalId, never on email alone: people change addresses and share them
ERP (products, prices, stock, orders)Pull products, prices and stock on a schedule: hourly, or every 15 minutes for stock. Push orders when paidThe ERP owns the catalogue. A price edited in the admin is overwritten on the next pull, so say so to the editors, or lock those fields
Fulfilment / 3PLPush the order when paid. Pull shipping status hourly, or take the 3PL's webhookHand-off must be idempotent: a warehouse that receives an order twice ships it twice. See the worked example
Marketing automationPush consent and list membership when a person opts in or out, within minutes. Campaigns are sent from thereConsent must reach it before the next campaign. Do not send bulk mail from jobs: why
Analytics and adsPush server-side conversion events (order paid, lead created) as they happen. Page analytics stay in the browserThese APIs accept duplicates silently and count them: send an event id they de-duplicate on
Chat (Slack, Teams)Push a notification to a channel webhook on a form, an order or a content change, as it happensThe webhook URL is the credential. The body is JSON with its own braces: build it as a value and json_encode it

Two platform facts shape all of them. Record actions cannot read or write orders, invoices, quotes or form submissions, so a job handles those only as the record of its own entity event; a scheduled job that must list or change them does it through the site's own REST API, as the worked example does. And a record written by an asynchronous job fires no entity events, while one written by a synchronous web-event job does; which of your writes start other jobs is on Field ownership and two-way sync.

Credentials for a third party

A credential never appears in a tracked file. Not in a template, not in a JSON body under templates/hooks/ (the folder keeps the module's old name, Hooks), not in a script in the repository. The repository deploys to every environment, its history keeps a key after it is deleted, and everyone who can clone it can read it. Keep it out of a job's own fields too, such as an HTTP Request's headers: read it from site data instead, so that each environment holds its own.

Where it lives. The one place a job can read a value it was not given is site data, so that is where an integration's key goes, under a key named for the integration:

Template code
Authorization: Bearer {$siteData->get('crm-api-key')}
Authorization: Bearer {{ siteData.get('crm-api-key') }}

Two facts about site data place a credential correctly. Site data is not a secret store, so a key kept there is protected by how little it can do, not by where it is kept. And site data does not travel with an export: importing a job to production does not bring its keys, and each environment holds its own, which is what you want.

The platform does encrypt credentials at rest, with authenticated encryption bound to the client, but only its own: AI provider keys and chat channel tokens. A job's templates cannot read either, and there is no encrypted store a job can read from. So make any key a job uses worth little outside its purpose:

  1. Scope the key on the other side to exactly what the integration does: create contacts, not delete them; read stock, not change prices.
  2. One key per environment, so a development site can never write to the production CRM.
  3. Rotate on a schedule and on every staff change, by adding the new key to the other system, changing the site data value, and deleting the old key once Batch Logs show the new one working.
  4. Put a service of your own in between when the credential can do real damage: payment refunds, ERP writes, anything that moves money. The service holds the real credential; the job holds only a token that lets it ask the service for one narrow thing.

A call into the site's own API

For a call the other way, from a server into the site's API, the preferred credential is an OAuth access token for a backend user made for the integration, kept fresh with its refresh token (Connect a server integration with OAuth and keep its token fresh). The refresh token changes on every use, so refreshing has to happen in one process, under a lock. A job cannot be that process: its runs are independent and can overlap, and site data has no update-if-unchanged operation, so two runs refreshing at the same time would invalidate each other's refresh token. A job that calls the site's API, as the worked example's jobs do, therefore uses an API key on the integration's backend user, the older credential, scoped to what the integration does. Say so in the job's Description.

Retry, backoff and dead letters

An HTTP Request's own retries cover seconds to minutes, and nothing else retries by itself, so an integration needs a policy for the other system being down for an hour: a scheduled resend, a dead letter after a day, and a report to a shared inbox. The policy, with concrete intervals, is on Retry, backoff and dead letters.

Field ownership

A two-way sync loops unless each field has one owning system and each direction sends only the fields its side owns. How to decide, record and enforce it is on Field ownership and two-way sync.

Go deeper

Was this page helpful?

Your answer goes to the docs team.