Guide 06 · Developer docs

The APIs: REST, GraphQL and custom endpoints

Every Core dna site answers three kinds of API request under /cdna-api: REST for everything, GraphQL for reads that span several entity types, and web events for endpoints you define yourself as jobs. Which fits which task, one worked request for each, and what is true of all three. Jobs were called hooks until the module was renamed Orchestration; the API's routes still use the old name.

L1–L3
Unit 04 · Custom endpoints without writing framework code

Securing a custom endpoint

What to add for each kind of caller, a signature check for a partner system, three things to add yourself, and the signs that an endpoint has outgrown a job.

Who this is for. Whoever publishes a custom endpoint: the integrations engineer whose partner calls the site, and the JavaScript developer whose page does. Afterwards you can choose the protection for an endpoint from who calls it, check a partner's signature in the job, and tell when the logic belongs somewhere other than a job.

Choose the protection by caller

What to add depends on who calls:

CallerProtection
Your own server, or a backend integrationTick the web event's authentication setting and send an OAuth access token (or, the older way, an API key). The platform then refuses everyone else with a 403 before any action runs.
A partner system that signs its requestsLeave authentication off and check the signature in a condition before anything else runs (below). Keep the shared secret in site data, not in the job.
A browser on your own siteAccept that it is public. Return only published, non-personal data; filter on the publish date, since a synchronous run sees unpublished records; keep it read-only.

Authentication admits backend users only: a signed-in site member is refused like a stranger. Whoever the caller is, the actions run as the job's author (Custom endpoints), so the protection decides who may start the job, never what the job may do.

A signature check

A partner that signs its requests sends a hash of the body, made with a secret you share, in a header. Make the job's first action a Condition of type template that recomputes the hash and compares; its else branch is an HTTP Response with status 401 and Stop execution on.

Template code
{$expected = hash_hmac('sha256', $event->getBody(), $siteData->get('partner-webhook-secret'))}
{$sent = $event->getHeader('x-signature')}
{if is_array($sent) && $expected == implode('', $sent)}{$result->setMatching(true)}{/if}
{# Verify a partner's signature before the request reaches the job, in a proxy or a small service of your own, or require authentication instead (see below). #}

Two details in the Smarty version are easy to get wrong. getHeader() returns the header's values as a list, under the header's name in lower case, so the comparison joins the list first and asks for x-signature, not X-Signature. And the hash is made from getBody(), the raw body exactly as sent; a re-encoded copy of getBodyData() would not match byte for byte. hash_hmac is one of the PHP functions the Smarty security policy allows in templates.

On a Twig site, verify a signature before the request reaches the job. Job templates render with the site's engine, so check it in a proxy or a small service of your own that then calls the endpoint with credentials, or give the partner a backend user and an OAuth client so that the authentication setting does the work.

Three things to add yourself

  • Rate limits. Put a rate limit in front of a public endpoint that does real work per call, such as sending an email or calling a paid service: at the CDN or proxy, not in the job.
  • Origin checks. Use the authentication setting or a signature check (above) to decide who may call the endpoint.
  • Methods. Untick every method the endpoint does not use; a method that is not ticked answers 404 as if the endpoint did not exist.
  • Repeats. A caller that retries after a timeout runs the job again. An endpoint that creates records should look for the record first, by an external id, and answer with the one already there.

When you are abusing it

  • The job has more than a handful of actions. Branches inside branches in an admin form are code without version control, review or tests. When the logic outgrows five or six actions, move it to the system that calls the site, and keep the job to the lookup or the write it has to make here.
  • It needs state between calls. Counting attempts, remembering a previous answer, holding a session: a job has siteData, which is one store shared by every call, not a database. Keep that state in a custom entity with proper records, or in the calling system.
  • You are debugging it by reading run logs. Orchestration › Batch Logs is good for finding which action failed, not for developing logic. If each change is a round of edit, call and read, the endpoint belongs in a service you can run and test locally, which calls the REST API for what it needs from the site.
Go deeper

Was this page helpful?

Your answer goes to the docs team.