Script Webhooks
Script Webhooks
Every script in the Script Engine has a webhook URL — a single HTTPS endpoint
that runs the script when you send it an HTTP POST. Use it to trigger a script from an
external application, a scheduled job on another system, a form submission, a Plex automation, or a
one-line curl command. Whatever JSON you post becomes the script's input.
Finding the webhook URL
Open a script in the Script Engine editor. Next to the script's ID chip in the header you'll see a Webhook button. Click it to open a popover that shows:
- the exact callable URL, with a Copy button;
- a Return mode toggle — see below;
- a ready-to-paste
curlexample that tracks the toggle; - a reminder of how to authenticate.
The URL follows this pattern, where {id} is the numeric Script ID:
POST https://data-magik.com/api/script-engine/scripts/{id}/execute
Authentication
Webhook calls authenticate with a DataMagik API key. Create one under
Your Account > API Keys; keys start with dcp_. Send it as a header:
X-API-Key: dcp_your_api_key_here
An Authorization: Bearer dcp_your_api_key_here header works identically. The key's
user must hold the DataMagik - Builder or DataMagik - Viewer
permission, and the script runs in that user's company context.
Sending data to the script
Post any JSON object as the request body — the script receives it verbatim as its
context. For example, this request:
curl -X POST https://data-magik.com/api/script-engine/scripts/482/execute \
-H "X-API-Key: dcp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "orderId": 12345, "notify": true }'
...is read inside the script like this:
function main(context) {
const id = context.orderId; // 12345
if (context.notify) { /* ... */ }
}
context. The body must be a JSON object; a bare
array, string, or number is rejected with a 400 error. You can optionally pin a script version or
priority with the X-Script-Version-Id and X-Script-Priority headers.What the webhook returns
There are two return modes, chosen per request with the wait query
parameter. The Webhook popover in the editor has a Return mode toggle for them and
rewrites both the URL and the curl example as you switch, so you can copy the one you
want rather than editing the URL by hand.
Queue and poll — the default
Leave wait off entirely and nothing changes from how webhooks have always behaved.
Short scripts finish inline; longer ones continue in the background so your caller never hangs.
- Finished in time — HTTP 200. The response includes
execution_id,completed: true, and anexecutionobject with the script'soutput_data, status, anyerror_message, run time, API-call count, and console logs. - Still running — HTTP 202. The response includes the
execution_idand astatus_url. PollGET /api/script-engine/executions/{execution_id}(or stream.../executions/{execution_id}/stream) to get the final result.
Wait for the result — ?wait=sync
Add ?wait=sync when the calling system cannot poll — it has no polling loop
and no callback endpoint, and needs the answer in the response to the request it just made.
curl -X POST "https://data-magik.com/api/script-engine/scripts/482/execute?wait=sync" \
-H "X-API-Key: dcp_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "orderId": 12345 }'
The request is held until the script finishes, and whatever your script returns is the entire response body — no wrapper, no envelope, no extra keys. You define the response exactly:
| Your script returns | The caller receives |
|---|---|
return { invoice: "INV-1042", total: 91.5 } | {"invoice":"INV-1042","total":91.5} |
return [1, 2, 3] | [1,2,3] |
return "OK" | "OK" |
return { result: 42 } | {"result":42} |
return (nothing) | {} |
Because the body belongs to you, the run's identity moves to response headers:
X-Execution-Id carries the execution id for tracing the run in history, and
X-Execution-Time-Ms carries how long the script itself took.
The status codes in this mode:
| Code | Meaning |
|---|---|
200 | Finished. The body is your return value. |
422 | The script ran and failed. There is no return value to pass through, so the body is {success, error, error_code, execution_id} instead. |
429 | Your company already has the maximum number of synchronous runs in flight. Retry shortly, or call without ?wait=sync. |
503 | The execution queue is unavailable, so the run never started. |
504 | Ran out of time — see below. |
Add &wait_timeout=30 (seconds) to shorten the wait if your own client gives up
sooner than we do and you would rather have our 504 than its dead socket. Values above
the ceiling are clamped, not rejected.
504 with
error_code: "SYNC_WAIT_TIMEOUT", the execution_id, and a
status_url.The run is not cancelled. It carries on in the background under its own timeout, and its result still appears in execution history under that
execution_id. A
504 here means “we could not hold the line”, not “the work was
lost”.Note this is deliberately a
504 and never a 202: a caller that asked to
wait has no polling loop, so any 2xx would read to it as success for a result it will
never collect.?wait=sync is the wrong shape for it. Use the default mode and poll, or have
the script deliver its own result — an email, a file drop, an outbound webhook — instead of
returning it.Where runs show up
Every webhook run is recorded in the script's execution history, with its console logs, output, and any errors — the same place scheduled and manual runs appear. If a call fails, check the response body first (auth or bad-JSON errors come back immediately), then the execution history for runtime errors inside the script.