Skip to content

Deliverect — Adapter Guide

Which integration is this? This is the consolidated knowledge pack for Deliverect in the new model (see ADR 0004); it absorbs the former expert-deliverect repo, now frozen. The field maps and behaviour below are grounded in the production kds-management-service/service/DeliverectService/ integration (the source of truth). In pos-integration-service Deliverect currently has a Go adapter under internal/adapters/deliverect/; the JS adapter scripts under scripts/adapters/deliverect/ are not yet built — this pack is the reference for generating them.

Provider Info

Field Value
Slug deliverect
Auth Type oauth (OAuth 2.0 client credentials)
Base URL Per-tenant: staging https://api.staging.deliverect.com, prod https://api.deliverect.com
API Docs https://developers.deliverect.com/
Model Push — Deliverect pushes menus/orders to us via webhooks; we push orders into Deliverect via outbound API. We are a Channel partner.
Sample fixtures samples/deliverect/ — see sync_products_input.json

Authentication

Outbound (Us → Deliverect): OAuth 2.0 client credentials.

POST {baseUrl}/oauth/token
{ "client_id": "...", "client_secret": "...",
  "audience": "{baseUrl}/oauth/token", "grant_type": "client_credentials" }
→ use Authorization: Bearer {access_token} on all outbound calls

Token is cached ~60 min and re-fetched on miss (client credentials has no refresh token — just request a new one). In the new model, client_id / client_secret come from the provider connection (apiKey / apiSecret or config), and the script exchanges them for the bearer token.

Inbound (Deliverect → Us): HMAC-SHA256 webhook verification.

  • Header x-server-authorization-hmac-sha256, value = HMAC-SHA256(webhookSecret, rawBody).
  • Inbound webhooks also carry X-Tenant-Id and x-sub-domain.
  • HMAC is verified in Go, not in the JS sandbox.

Credential fields (all required)

base_url, client_id, client_secret, account_id, webhook_secret, channel_name. (channelLinkId — the Deliverect↔Cata channel link — lives in outlet_providers.settings.)

Integration Architecture

Inbound webhooks (Deliverect → Us):

Webhook Path Topic
Store registration POST /v1/delphinus/store sync_outlet
Menu sync POST /v1/delphinus/menu sync_products (async)
Product snooze POST /v1/delphinus/product/snooze snooze_items
Busy mode POST /v1/delphinus/store/status (busy mode)
Order status POST /v1/delphinus/order order_status_update

Outbound API (Us → Deliverect):

Call Method & Path
Get token POST {baseUrl}/oauth/token
Create order POST {baseUrl}/{channelName}/order/{channelLinkId}
Cancel order POST {baseUrl}/{channelName}/order/{channelLinkId} (status 100)
Courier update POST {baseUrl}/{channelName}/courierUpdate/{channelLinkId}
Get locations GET {baseUrl}/locations?where={"account":"{accountId}"}&max_results=500

Data Format

Type Rule Example
Prices (inbound) integer cents → divide by 100 150015.00
Prices (outbound) × 100, double-rounded 15.501550
Tax rates integer → divide by 1000 1000010.0%
Timestamps ISO-8601 or YYYY-MM-DD HH:MM:SS
Content-Type application/json

Outbound rounding: round(round(amount*100)/100 * 100) (avoids float drift). Tax sentinel: a tax of 0/negative is stored as -1 = "use default tax".

Quirks & Constraints

Quirk Detail
Prices are cents Divide by 100 inbound, multiply ×100 outbound. (Contrast: Revel is decimal.)
Async menu processing The menu webhook stores the raw payload and queues it for async processing to dodge the 30s webhook timeout.
Bundle detection isCombo=true or isVariant=true → treated as a bundle; bundle items resolve from both the Modifiers and Products maps.
Upsell skipped Modifier groups with isUpsell=true are skipped entirely.
Description truncation Item descriptions truncated to 245 runes (multibyte-safe).
Modifier input type SINGLE when min=1,max=1; else MULTIPLE. AllowMultipleQty when max>1 && multiMax>=max.
Translations Names/descriptions resolved against the tenant DEFAULT_LANGUAGE via nameTranslations/descriptionTranslations.
Item dedup Items keyed {storeId}-{itemCode}; availability flags merged (OR) across menus.
Cancel reuses create endpoint Cancel = create endpoint with status=100, reason "reject_by_outlet".
Delivery finalize = serve For DELIVERY, status 90 maps to ServeOrder (driver still delivering), not complete.
Location coordinates GeoJSON: coordinates[0]=longitude, [1]=latitude.

Mappings reference

These maps come from the production kds integration; the JS adapter should emit the Cata Standard API shapes.


Topic: test_connection

Verify credentials by obtaining a token and/or calling GET /locations.

Topic: list_remote_outlets

GET {baseUrl}/locations?where={"account":"{accountId}"}&max_results=500 → return { id: location._id, name: address.restaurantName, address: address.street }.

Topic: sync_outlet

Inbound store-registration webhook (POST /v1/delphinus/store). Our registration response returns the other webhook URLs (statusUpdateURL, menuUpdateURL, snoozeUnsnoozeURL, busyModeURL).

Cata field Source
externalId / locationId location._id
name address.restaurantName
address.line1 / city / state / country / postalCode address.street / city / stateOrProvince / country / postalCode
lon / lat address.coordinates.coordinates[0] / [1] (GeoJSON)
timezone location.timezone
openingHours location.openingHours[]{"monday":["09:00-22:00"]} (dayOfWeek 1=Mon…7=Sun)
contact location.contact.phoneNumber
channelLinkId request.channelLinkId

Samples: sync_outlet_input.jsonsync_outlet_output.json.

Topic: sync_products

Inbound menu webhook (POST /v1/delphinus/menu), processed async. Flatten the Deliverect menu tree to Cata products.

Product:

Cata field Source Transform
itemCode product.plu primary identifier
name / description product.name / description via getTranslation(); description truncated 245 runes
basePrice product.price / 100
visible always true
pickupAvailable / deliveryAvailable / eatInAvailable menuType 0=both, 1=delivery, 2=pickup, 3=eat-in
taxOverride / …Delivery / …EatIn takeawayTax / deliveryTax / eatInTax / 1000 (or -1)
isBundle isCombo / isVariant true if either (subject to internal-bundle overrides)
posCategory parent category.name

Modifier group: name via translation; inputType SINGLE(min=1,max=1)/MULTIPLE; minSelect/maxSelect from min/max (or -1); itemCode from modifierGroup.plu. Modifier option: itemCode from modifier.plu; additionalPrice = price/100; asDefault from defaultQuantity; tax /1000. Bundle section (isCombo): itemCode/name/min/max from the modifier group; bundle item: itemCode + price (surcharge/100) from the Modifiers/Products map.

Samples: sync_products_input.jsonsync_products_output.json.

Topic: order_dispatch

POST {baseUrl}/{channelName}/order/{channelLinkId} with a bearer token.

Deliverect field Cata source Transform
channelOrderId order.uuid direct
channelOrderDisplayId order.dailyQueueNo direct
orderType order.deliveryMethod PICKUP→1, DELIVERY→2, EATIN→3
orderIsAlreadyPaid order.paidAt true when paid
items[].plu/name/quantity/remark itemCode/itemName/qty/notes direct
items[].price itemOnlyPrice × 100
items[].subItems[] modifiers[] plu/name direct, price ×100, qty default 1
bundle children bundleItem.surcharge nested subItems, price ×100, qty 1
payment.amount order.totalPay × 100; payment.type 0 (CC online)
deliveryCost deliveryFee - deliveryFeeDiscount ×100
discountTotal discountItem + pointsToDiscount negative, ×100
bagFee / tip packaging fees / tipsAmount ×100
serviceCharge (totalPay - subTotal - deliveryFee - bagFee - tips + discountTotal) ×100

Customer/delivery address are set for DELIVERY orders (recipient name, phone, street, lat/long). Samples: order_dispatch_input.jsonorder_dispatch_output.json.

Topic: order_status_update

Inbound POST /v1/delphinus/order. Look up the Cata order by channelOrderId.

Deliverect status codes → Cata

Code Meaning Cata action/status
10 New (received)
20 Accepted ACCEPTED (also submit delivery for DELIVERY)
40 Printed
50 Preparing IN PROGRESS
60 Prepared
70 Pickup ready READY
90 Finalized COMPLETED (non-delivery); READY/serve for DELIVERY
95 Auto-finalized same as 90
100 Should-cancel CANCELLED
110 Cancelled CANCELLED (reason reject_by_outlet)
120 Failed CANCELLED

Samples: order_status_webhook.jsonorder_status_output.json.

Optional topics

  • snooze_items — inbound POST /v1/delphinus/product/snooze: operations[0].data.items[].plu + snoozeEnd/snoozeStart, keyed to store by channelLinkId.
  • Busy mode — inbound POST /v1/delphinus/store/status (status e.g. busy/available). Not a standard Cata topic.
  • Cancel order / courier update — outbound, reuse the create/courier endpoints.

Changelog

2026-06-09 — Consolidated from expert-deliverect

Imported the Deliverect knowledge pack (auth, architecture, data format, status codes, per-topic field maps, samples) into pos-integration-service. Corrected this guide's auth from a flat api_key/Bearer sketch to the real OAuth 2.0 client-credentials flow, fixed the order-create endpoint (/{channelName}/order/{channelLinkId}, not /v1/orders), and the status codes (10/20/40…120, not 1–5). Cents handling confirmed correct for Deliverect.

Verification log

Topic Last verified Environment Notes
sync_outlet kds prod service/DeliverectService store registration + GET /locations
sync_products kds prod service/DeliverectService async menu webhook, 3 product models
order_dispatch kds prod service/DeliverectService create order, cents ×100
order_status_update kds prod service/DeliverectService status codes 10–120

Open uncertainties (for the JS adapter)

  • Exact Cata Standard API field names vs the kds internal model used above.
  • Whether account_id / channel_name live in provider_connections.config or outlet_providers.settings in the new model.
  • Courier-update / busy-mode: in scope for the JS adapter or Go-only?