Webhooks

Webhooks let applications start workflows through HTTP while WOML owns validation, authentication, deduplication, and durable admission.

Declare a webhook

WOML
<webhook id="newOrder" path="/webhooks/orders" method="POST" auth="bearer" secret="{{secrets.ORDER_WEBHOOK_TOKEN}}">
  <schema>
    { "type": "object", "required": ["orderId"], "properties": { "orderId": { "type": "string" } } }
  </schema>
</webhook>

path is a static absolute route. auth is required and accepts bearer or explicitly insecure none. Bearer authentication requires an exact secret reference.

Request behavior

The optional inline schema uses JSON Schema Draft 2020-12. Invalid input returns HTTP 400 and creates no run. Accepted input returns HTTP 202 with a durable run ID; execution continues asynchronously. Request bodies are limited to 1 MiB.

Test the endpoint

woml run prints the exact URL and a copyable curl command at startup. Add the configured bearer token and JSON body. Use context.payload.orderId in workflow scripts.

Never expose auth="none" to untrusted traffic without an authenticating reverse proxy.

Build and test a webhook workflow

Create orders.woml:

WOML
<woml>
  <workflow id="orders" name="Receive orders" version="1.0.0">
    <triggers>
      <webhook
        id="newOrder"
        path="/webhooks/orders"
        method="POST"
        auth="none"
      >
        <schema>
          {
            "type": "object",
            "required": ["orderId", "amount"],
            "properties": {
              "orderId": { "type": "string", "minLength": 1 },
              "amount": { "type": "number", "minimum": 0 }
            },
            "additionalProperties": false
          }
        </schema>
      </webhook>
    </triggers>
    <steps>
      <step id="accept" name="Accept order">
        <script>
          return {
            orderId: context.payload.orderId,
            acceptedAmount: context.payload.amount
          };
        </script>
      </step>
    </steps>
  </workflow>
</woml>

Run it locally:

Terminal
woml run orders.woml

WOML prints the active URL and a generated curl example. From another terminal, send:

Terminal
curl --request POST http://127.0.0.1:3000/webhooks/orders \
  --header 'content-type: application/json' \
  --data '{"orderId":"order-42","amount":120}'

The HTTP response confirms admission with status 202 and a durable run ID. It does not wait for the full workflow result. Follow the run in the WOML terminal or inspect it by ID.

Add bearer authentication

Store a token without putting its value in source:

Terminal
woml secrets set ORDER_WEBHOOK_TOKEN

Change the trigger to auth="bearer" and add secret="{{secrets.ORDER_WEBHOOK_TOKEN}}". Call it with an Authorization header:

Terminal
curl --request POST http://127.0.0.1:3000/webhooks/orders \
  --header 'authorization: Bearer YOUR_TOKEN' \
  --header 'content-type: application/json' \
  --data '{"orderId":"order-42","amount":120}'

Do not write the real token in command history on a shared machine; use an environment variable or another secure client mechanism.

Understand validation failures

Malformed JSON, a body larger than 1 MiB, or a payload rejected by the Draft 2020-12 schema creates no run. The caller receives an HTTP error with a WOML diagnostic code. A valid request can still create a run that later fails in a step; that is a workflow failure, not an HTTP admission failure.

Design stable routes

Webhook paths are static absolute routes. Parameters, wildcards, repeated slashes, and the reserved /_woml prefix are rejected. Put variable identity such as an order ID in the JSON body instead of the route.