How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Create your own JSON endpoint with a synchronous job

A synchronous web-event job is an endpoint built in the admin. Retrieve a record from the request, branch on whether it was found, and reply with encoded JSON: a 200 with the record or a 404 with an error, and nothing deployed.

Context

Context. Your front end needs an answer the REST API does not give in one call: a lookup by a field that is not a filter, a small calculation, a response in another system's format. A synchronous web-event job answers it with no framework code and no deploy. This builds the endpoint used to test Custom endpoints: given a page slug, it replies with the page's id, name and path, or a JSON 404. Everything is set in the admin under Customization › Orchestration; the module used to be called Hooks, and a job a hook, which is why its URLs still say webhook. For an endpoint that takes a POST body and writes a record, follow the POST example in the same guide.

Steps

  1. Create a job. Tick Synchronous and limit it to your site. Leave it unpublished while you build it.
  2. Add a web event trigger. Tick only GET. Leave authentication off only if the endpoint returns nothing a visitor should not see; note the URL it shows.
  3. Add a Retrieve records action named find-page: entity pages, criteria below, limit 1.
  4. Add a Condition named found of type template, with the condition template below.
  5. Inside its branch, add an HTTP response: status 200, header Content-Type: application/json, the found body, stop execution on.
  6. Add an Else branch with an HTTP response: status 404, the same header, the missing body.
  7. Publish the job and call the URL. Check the run under Batch Logs if the reply is not what you expect.

Code

Criteria for find-page: {"slug": "{$event->getQueryParameter('slug')}"}. The field is JSON, not a template: the platform decodes it first and then renders each value on its own, which is why the braces of the JSON need no {literal} here while a response body does.

The condition template. It matches only if it calls setMatching(true):

Template code
{if $runData->get('find-page')->record}{$result->setMatching(true)}{/if}
{% if runData.get('find-page').record %}{% do result.setMatching(true) %}{% endif %}

The found body:

Template code
{$page = $runData->get('find-page')->record}
{$payload = ['id' => $page->getId(), 'name' => $page->getName(), 'path' => $page->getFullPath()]}
{$payload|json_encode}
{% set page = runData.get('find-page').record %}
{{ {'id': page.getId(), 'name': page.getName(), 'path': page.getFullPath()}|json_encode }}

The missing body:

Template code
{$payload = ['error' => 'not_found', 'slug' => $event->getQueryParameter('slug')]}
{$payload|json_encode}
{{ {'error': 'not_found', 'slug': event.getQueryParameter('slug')}|json_encode }}

Calling it:

Shell
curl -i 'https://www.example.com/cdna-api/webhook/YOUR-WEB-EVENT-SLUG?slug=apis'
# HTTP/1.1 200 OK
# Content-Type: application/json
# {"id":1521,"name":"APIs: REST, GraphQL and custom endpoints","path":"/developer-docs/apis"}

curl -i 'https://www.example.com/cdna-api/webhook/YOUR-WEB-EVENT-SLUG?slug=no-such-page'
# HTTP/1.1 404 Not Found
# {"error":"not_found","slug":"no-such-page"}

Why this and not that

Not JSON typed into the body: in Smarty a { starts a tag, so a hand-typed {"error":"not_found"} fails when the job runs and the caller gets a 500 naming your job; encoding a value never fails. Not an asynchronous job: it answers at once with the batch it queued, not with your result. Not one response action with the status chosen by a template: a condition with two replies shows in Batch Logs which branch ran. And before publishing an open endpoint, remember two things about it. It runs as the job's author, not as the caller, and it sees unpublished records: this endpoint returns an unpublished page to anyone who knows its slug. Test getPublished() on the record in the condition if that matters.

Go deeper

Was this page helpful?

Your answer goes to the docs team.