Webhook - Call a script
This example shows how to expose a webhook endpoint that delegates the request to a run-script action and lets the script build the HTTP response.
Create a user with an API key assigned to the
/internal/data-accessgroup.Define a webhook endpoint declaring its HTTP verbs in
methods, each with an optionallinkedAction.Read the request with
webhook.parseArgs(trigger.parameters)(.method(),.body(),.json(), …).Build the HTTP response with
request.respond(code, content, contentType).
git checkout origin/osp-web-configuration .
git checkout origin/osp-scripts-configuration .
git checkout origin/example-webhook-call-script .
Configuration structure
How it works
The endpoint declares a single
POSTverb in itsmethodslist. A request using a verb that is not declared is rejected with a405 Method Not Allowed.The script parses the request with
const request = webhook.parseArgs(trigger.parameters), then reads it throughrequest.method(),request.headers()/request.header(name),request.body()/request.json()(POST/PUT) andrequest.query()(GET/DELETE).The script returns the HTTP response with
request.respond(code, content, contentType):codesets the HTTP status code (default200),contentsets the response body, andcontentTypesets itsContent-Type. Userequest.respond(code)for a status-only response with no body.The caller must hold
WRITEaccess on both the endpoint and the linked action.
See webhooks for the full reference.
Steps
Setup a user with an API key
To trigger the webhook, the user must be member of the group
/internal/data-access.modules/keycloak/keycloak-1/users.keycloak{ "users": [ { "enabled": true, "groups": [ "/internal/data-access" ], "username": "example", "email": "example@localhost", "firstName": "example", "lastName": "example", "credentials": [ { "initial": true, "temporary": true, "type": "password", "value": "mysuperpassword" } ], "attributes": { "authorizedKeys": [], "apiKey": "simple-api-key" } } ] }
Warning
When defining an API key for a user, it may take up to 1 minute for it to be applied.
Declare the script that handles the request
The script parses the request with
webhook.parseArgs(trigger.parameters)and builds the response withrequest.respond(code, content, contentType).root/script/detached.scripts{ "moduleId": "modules.scripts.scripts-1", "sourceFile": "root/script/handle-request.js" }
root/script/handle-request.js// Linked script executed by the webhook endpoint "root.webhook.script-endpoint". // // When a script is triggered by a webhook, the incoming HTTP request is carried by the script // parameters. Parse them with "webhook.parseArgs(trigger.parameters)" to obtain a request // object that exposes the request and the response builder: // * request.method() the HTTP method (POST, GET, PUT or DELETE), // * request.headers() all request headers as a key/value object, // * request.header(name) a single header value, // * request.body() the raw request body (POST / PUT), // * request.json() the request body parsed as JSON, // * request.query() the query parameters (GET / DELETE), // * request.respond(code) sets an HTTP status code with no body, // * request.respond(code, content, contentType) sets the HTTP response returned to the caller. // // respond() does not stop the script: the last call wins and the response is read once the script ends. // The script is wrapped in a function so "return" can be used to short-circuit after responding. const request = webhook.parseArgs(trigger.parameters); (function () { const contentType = request.header("Content-Type") || request.header("content-type") || "unknown"; let payload; try { payload = JSON.parse(request.body()); } catch (parseError) { // The script controls the HTTP status code returned to the caller and produces the final body. request.respond(400, JSON.stringify({ error: "The request body is not valid JSON." }), "application/json"); return; } const temperature = Number(payload.temperature); if (!Number.isFinite(temperature)) { request.respond(422, JSON.stringify({ error: "Field 'temperature' is required and must be a number." }), "application/json"); return; } const status = temperature > 30 ? "alert" : "ok"; log.info("Webhook script computed status [{}] for temperature [{}].", status, temperature); request.respond(201, JSON.stringify({ status: status, temperature: temperature, receivedContentType: contentType }), "application/json"); })();
Declare the run-script action
The action is of type
RUN_SCRIPTand is issued by theosp-webmodule that exposes the webhook.root/action/action.ospp{ "moduleId": [ "modules.web.web-1" ], "type": "RUN_SCRIPT" }
Create the webhook endpoint
The endpoint declares a single
POSTverb in itsmethodslist, whoselinkedActionreferences the action. The request is read by the script through thewebhookcontroller, so the actionargumentslist is left empty.root/webhook/script-endpoint/endpoint.webhook{ "moduleId": "modules.web.web-1", "path": "/call-script", "methods": [ { "verb": "POST", "linkedAction": { "actionId": "root.action", "parameters": { "scriptId": { "type": "CONSTANT", "value": "root.script" }, "arguments": { "type": "LIST", "values": [] } }, "expiration": { "value": 5, "unit": "SECONDS" } } } ] }
Send an HTTP POST request
Using an HTTP client of your choice, send a
POSTrequest to the endpoint. The script status code and content are returned to the caller.curl -X POST http://stack-1.onsphere.local:5000/osp/webhooks/call-script \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer simple-api-key" \ -d '{"temperature": 35}'
wget -qO- --method=POST \ --header="Accept: application/json" \ --header="Content-Type: application/json" \ --header="Authorization: Bearer simple-api-key" \ --body-data='{"temperature": 35}' \ http://stack-1.onsphere.local:5000/osp/webhooks/call-script
http POST http://stack-1.onsphere.local:5000/osp/webhooks/call-script \ Accept:application/json \ Authorization:"Bearer simple-api-key" \ temperature:=35
POST http://stack-1.onsphere.local:5000/osp/webhooks/call-script HTTP/2.0 Accept: application/json Content-Type: application/json Authorization: Bearer simple-api-key { "temperature": 35 }
The script answers with the status code
201and the following body:{ "status": "alert", "temperature": 35, "receivedContentType": "application/json" }
Sending a body without a
temperaturefield makes the script answer with a422status code, and a body that is not valid JSON makes it answer with400.