Run top to bottom after every deployment. Steps 1–5 are non-destructive. Steps 6–7 write to Chatwoot and must use a test conversation.
Set up once per shell:
BASE=https://eks-relay.easyklima.com
SECRET=... # RELAY_SHARED_SECRET, read from .env — do not echo it
ADMIN=... # ADMIN_TOKEN
curl -s -o /dev/null -w '%{http_code}\n' $BASE/health # expect 200
curl -s $BASE/health | jq
Expect ok: true, a service name, and an uptime. No secrets in the body.
curl -s $BASE/ready | jq
Expect ok: true and a dependencies block. Each dependency reports
{ok, status, target} where target is host+path only. configured is a set
of booleans — it says whether a secret is set, never what it is.
database and chatwoot must be ok. woocommerce / wpStore may report
non-200 if the shop's Cloudflare challenges the call; that is a routing issue,
not a relay fault.
curl -s -o /dev/null -w '%{http_code}\n' -X POST $BASE/tools/get_payment_methods \
-H 'Content-Type: application/json' -d '{}' # expect 401
curl -s -o /dev/null -w '%{http_code}\n' -X POST $BASE/tools/get_payment_methods \
-H 'Content-Type: application/json' -H 'Authorization: Bearer wrong' -d '{}' # expect 401
curl -s -X POST $BASE/tools/get_payment_methods \
-H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
-d '{"language":"pl"}' | jq
curl -s -X POST $BASE/tools/get_shipping_data \
-H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
-d '{"language":"pl"}' | jq '.data[:3]'
curl -s -X POST $BASE/tools/get_shipping_data \
-H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
-d '{"country":"DE","language":"de"}' | jq
get_payment_methods needs admin-level Woo consumer keys; if the key is
read-only it answers 200 with ok:false, code:"NOT_IMPLEMENTED" — that is the
designed degradation, and the Flowise tool surfaces it as an explanation.
Use a conversation id that exists in Chatwoot but has no live customer.
MSG=$RANDOM
PAYLOAD=$(cat <<JSON
{
"event": "message_created",
"id": $MSG,
"message_type": "incoming",
"content_type": "incoming_email",
"content": "Test techniczny relaya — prosze zignorowac.",
"conversation": {
"id": 1311, "inbox_id": 1, "channel": "Channel::Email",
"labels": [], "custom_attributes": {},
"additional_attributes": {"mail_subject": "Relay smoke test"},
"meta": {"sender": {"id": 1, "name": "Relay Test", "email": "relay-test@example.com"}}
},
"sender": {"id": 1, "name": "Relay Test", "email": "relay-test@example.com"}
}
JSON
)
# First delivery → 202 with a jobId
curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' -d "$PAYLOAD" | jq
# Same message id again → 200 with duplicate:true and NO new job
curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' -d "$PAYLOAD" | jq
Confirm in the DB:
curl -s "$BASE/admin/messages?conversationId=1311" -H "Authorization: Bearer ***" | jq
curl -s "$BASE/admin/events?conversationId=1311" -H "Authorization: Bearer ***" | jq
curl -s "$BASE/admin/jobs" -H "Authorization: Bearer ***" | jq
curl -s "$BASE/admin/tickets" -H "Authorization: Bearer ***" | jq
curl -s "$BASE/admin/meta" -H "Authorization: Bearer ***" | jq
Without a bearer token every /admin/* endpoint must return 401. The read-only
operator panel shell is at https://eks-relay.easyklima.com/ops; it contains no
data and asks for ADMIN_TOKEN in the browser before calling /admin/*.
Expect exactly one ProcessedMessage row for that message id and exactly one
job. On the server the same data is available offline:
sudo docker compose exec relay npm run events -- --conversation 1311
sudo docker compose exec relay npm run events -- --jobs
Non-actionable events must be skipped with 200:
for t in '"outgoing"' ; do
curl -s -X POST $BASE/webhooks/chatwoot -H 'Content-Type: application/json' \
-d "{\"event\":\"message_created\",\"id\":$RANDOM,\"message_type\":$t,\"content\":\"x\",\"conversation\":{\"id\":1311}}" | jq -c
done
# expect {"ok":true,"skipped":true,"reason":"not_incoming:outgoing"}
curl -s -X POST $BASE/tools/new_ticket \
-H 'Content-Type: application/json' -H "Authorization: Bearer $SECRET" \
-d '{"conversationId": <TEST_CONV_ID>, "summary": "Smoke test handoff"}' | jq
Expect {"ok":true,"ticketNumber":"EKS-YYYYMMDD-<id>","status":"created"}.
Call it a second time — expect the same number with "status":"existing".
In Chatwoot verify the conversation now has the ticket label and a
ticket_number custom attribute. Afterwards, remove the label and clear the
attribute to return the test conversation to its previous state.
aiac-aws@easyklima.com.GET /admin/events?conversationId=<id> → webhook_accepted, then
flowise_response, then reply_sent.chatwoot:<id>.ticket
label, the ticket_number, and that further messages log
skipped_ticket_mode instead of calling Flowise.Point the Chatwoot webhook back at the PHP relay URL. The PHP deployment on the shop's web server is untouched and remains functional.
Commit f36fc3d, host acmycar / 18.168.156.244, domain
https://eks-relay.easyklima.com.
| Step | Result |
|---|---|
docker compose config |
OK, one service |
docker compose ps |
eks-relay Up (healthy) |
GET /health |
200, mode: prod |
GET /ready |
200, ok: true — database, chatwoot, flowise, woocommerce, wpStore all ok |
/tools/* without a bearer |
401 UNAUTHORIZED on all seven endpoints |
/tools/get_payment_methods |
200, gateways returned (Przelewy24, PayPal, Stripe, Bancontact, bank transfer, …) |
/tools/get_shipping_data (zones) |
200, zones with ISO country codes |
/tools/get_shipping_data (zoneId=22, language=cs) |
200, DHL/DHL-COD priced in CZK — multicurrency path confirmed |
/tools/get_product_data (search=gaz) |
200, published products with prices |
/tools/get_car_data (VW, 2016) |
200, model disambiguation list |
/tools/get_order_data (bogus order) |
403 UNAUTHORIZED, no third-party data leaked |
| Webhook, delivery 1 | 202 + jobId |
| Webhook, delivery 2 (same message id) | 200 duplicate:true, no second job |
| Webhook, outgoing message | 200 skipped: not_incoming:outgoing |
| Worker outcome | job done; ProcessedMessage = skipped/ticket_mode; audit webhook_accepted → skipped_ticket_mode |
/tools/new_ticket (test conversation 1600) |
EKS-20260820-1600, status: created |
/tools/new_ticket again |
same number, status: existing |
| Chatwoot after ticket | label ticket + ticket_number + handoff + handoff_reason set |
| Follow-up message on that conversation | conversation fetched from Chatwoot, skipped_ticket_mode, Flowise not called |
git pull + docker compose restart |
new code live in ~0.4 s, no rebuild, no dependency reinstall |
RELAY_MODE=dev + file touch |
process reloaded in place (uptime 47 s → 12 s), no restart, no rebuild |
Test conversation 1600 was restored afterwards (labels and custom attributes
cleared, local Ticket row deleted). Mode was set back to prod.
Not yet run: step 7, the end-to-end e-mail test. It needs the Chatwoot webhook repointed at this relay, which is a deliberate, separately approved change.