Order Dispatch — Built-In Transformer (Cata-built POS adapter)¶
If you're new to order dispatch, read How Order Dispatch Works first — it explains both delivery paths (webhook vs Cata-built transformer) and when each one applies. This page is the hands-on walkthrough for the transformer path.
When a POS vendor doesn't host a webhook receiver, Cata can build the integration in-house: a per-provider JS transformer rewrites the standard Cata Order JSON into the POS's native API shape and POSTs directly to the POS. The internal caller selects this path with one optional field on the dispatch request:
POST /api/v1/orders/dispatch
{
"outletId": "...",
"event": "order.paid",
"order": { ... },
"builtInTransformer": "revel" // ← opt-in
}
If builtInTransformer is omitted, the standard webhook flow runs (Cata Order JSON + HMAC headers to the registered callback URL). The two paths are independent and can coexist on the same outlet.
When to use it¶
| Situation | Path |
|---|---|
| Partner runs their own webhook receiver | Standard — they consume Cata Order JSON, verify HMAC, transform on their side. See Order Dispatch (Vendor Integration). |
| POS exposes a public order-creation API but the operator can't / won't host a webhook | Built-in transformer — Cata writes the JS, operator just enters credentials. This page. |
What lives where¶
| Piece | Where | Purpose |
|---|---|---|
| Standard envelope script | central.adapter_scripts row (_standard, order_dispatch) |
Always runs first. Builds {url, method, headers, body=null} from the registered webhook. |
| Per-provider transformer | central.adapter_scripts row (<provider>, order_dispatch) |
Runs only when builtInTransformer="<provider>". Rewrites body / headers / URL for the POS's API. |
| Per-outlet credentials | tenantdb.outlet_providers.settings (JSON column) |
API keys, establishment IDs, dining-option mappings — anything outlet-specific. The transformer reads these via input.outlet.settings. |
| HMAC headers | injected by Go (internal/connectors/dispatch_pipeline.go) |
Added only on the no-transformer path. Transformer path is responsible for its own auth. |
Provider status¶
| Provider | Status | Script |
|---|---|---|
| Revel | 🟢 Sandbox-validated (current v1.2.0) — covers simple orders, modifiers, bundles (is_combo + products_sets[]), discounts, fees, all three dining options |
scripts/adapters/revel/order_dispatch.js |
| Atlas Kitchen | To do | — |
| Lightspeed | To do | — |
| Deliverect | N/A — Deliverect is a partner who registers their own webhook (standard path) | — |
The rest of this page walks through Revel as the worked example. New providers follow the same shape: write scripts/adapters/<provider>/order_dispatch.js, deploy, document outlet_providers.settings requirements, sandbox-validate.
Walkthrough — using the Revel built-in transformer¶
Step 1. Confirm the script is deployed¶
The transformer script lives in central.adapter_scripts keyed by (provider, topic). Deploy from source:
go run cmd/deploy-scripts/main.go --dry-run # what would happen
go run cmd/deploy-scripts/main.go # write to DB (UPSERT keyed by provider+topic)
You should see at least:
_standard / order_dispatch v1.0.0 (active)
revel / order_dispatch v1.2.0 (active)
Without the revel row, requests that pass builtInTransformer:"revel" get HTTP 400 (unknown built-in transformer "revel").
Step 2. Map the outlet to the provider¶
Stash credentials in outlet_providers.settings for the outlet. Required fields per the script's contract (see docs/guides/adapters/revel.md for the full list):
curl -sS -X PUT "$BASE/api/v1/outlet-providers/$OUTLET" \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: $TENANT" \
-d '{
"providerSlug": "revel",
"externalId": "<revel-establishment-id>",
"externalName": "<your outlet>",
"settings": {
"baseUrl": "https://<your-revel>.revelup.com",
"apiKey": "<real-key>",
"apiSecret": "<real-secret>",
"establishmentId": <int>,
"pickupDiningOption": <int>,
"deliveryDiningOption": <int>,
"eatinDiningOption": <int>,
"paymentTypeId": 7,
"serviceFeeAlias": "Cata Fee"
}
}'
Notes:
- The script throws if
apiKey,apiSecret,establishmentId, or the dining option matching the order'sdeliveryMethodis missing — the dispatch lands asdispatched: falsewith the exact reason. baseUrlis what reroutes outbound traffic to real Revel. Leave it unset during local testing if you want the transformer to inherit the registered webhook URL (so the same Hookdeck/webhook.site receiver gets both paths).- The
secrethalf of the HMAC is on the webhook record, NOT here. The transformer path doesn't HMAC.
Step 3. Dispatch with the opt-in flag¶
curl -sS -X POST "$BASE/api/v1/orders/dispatch" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $KEY" \
-d '{
"outletId": "'"$OUTLET"'",
"event": "order.paid",
"builtInTransformer": "revel",
"order": { ...same Cata Order JSON as the standard path... }
}'
A successful run returns:
{
"code": 200, "isSuccess": true, "message": "order dispatched",
"orderId": "<uuid>", "dispatched": true, "reason": ""
}
What landed at Revel (or your test receiver):
| Item | Value |
|---|---|
| URL | <settings.baseUrl>/specialresources/cart/submit (or the registered webhook URL when baseUrl is unset) |
| Method | POST |
Content-Type |
text/plain (legacy Revel parity) |
API-AUTHENTICATION |
<apiKey>:<apiSecret> from settings |
X-Cata-Signature / X-Cata-Event / X-Cata-Delivery-ID / X-Cata-Timestamp |
absent — auth is the script's responsibility on the transformer path |
| Body | Revel cart-submit JSON: {skin:"WebPortal", establishmentId, items[].product, items[].modifieritems, orderInfo, paymentInfo, …} — see docs/guides/adapters/revel.md for the field map |
Step 4. Verify locally before pointing at real Revel¶
There's a one-shot smoke test that exercises both paths against the same Hookdeck receiver so the difference is obvious side-by-side:
export KEY=<partner-api-key>
./script/demo/test-order-dispatch.sh setup <outletId> https://<receiver>
./script/demo/test-order-dispatch.sh standard <outletId> # Cata Order JSON + HMAC
./script/demo/test-order-dispatch.sh transformer <outletId> # Revel cart-submit, no HMAC
./script/demo/test-order-dispatch.sh unknown <outletId> # 400 with "unknown built-in transformer"
A short Hookdeck-receiver smoke recipe is the fastest local end-to-end check before pointing dispatch at real Revel.
Step 5. Cut over to real Revel¶
Once you've verified locally:
- Update
outlet_providers.settings.baseUrlto the real Revel base URL (the transformer derives<baseUrl>/specialresources/cart/submit). - Replace stub credentials with the real
apiKey,apiSecret,establishmentId, dining-option IDs, etc. - Decide what the standard path should do for this outlet:
- If you want production calls only via the transformer — delete the webhook (
DELETE /api/v1/webhooks/{webhookId}) so a stray request withoutbuiltInTransformerdoesn't accidentally POST to Hookdeck. - If both paths are valid — replace the webhook URL with your real receiver via
PUT /api/v1/webhooks/{webhookId}.
- If you want production calls only via the transformer — delete the webhook (
Watch the first few real calls for 422 responses — that's the signal we got a field shape wrong. The Revel transformer's "Known uncertainties remaining for sandbox" are listed in docs/guides/adapters/revel.md.
Going further: building a transformer for a new provider¶
Adding a new POS = writing a transformer script and deploying it. No Go changes are needed for the dispatch flow.
- Read the contract. Adapter Script Contracts →
order_dispatchdescribes the input shape (input.order,input.outlet.settings,input.event,input.standard) and the required output shape (url,method,headers,body). - Write
scripts/adapters/<provider>/order_dispatch.jswith@provider,@topic,@versionJSDoc tags. Throw on missing required settings; return the spec for a successful build. - Document settings + field map in
docs/guides/adapters/<provider>.md. - Deploy with
go run cmd/deploy-scripts/main.go. - Smoke-test locally using the same
script/demo/test-order-dispatch.sh transformerflow, just with your provider name. - Sandbox-validate against the real POS API; record open uncertainties under "Known uncertainties remaining for sandbox" in the provider doc.
See also¶
- Order Dispatch — Vendor Integration Walkthrough — the standard webhook flow, end-to-end.
- Orders Guide — pipeline architecture & request schema.
- Adapter Script Contracts — the JS input/output contract per topic.
- Provider — Revel — full Revel field map, settings reference, sandbox open questions.