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 $ADMIN" | jq
curl -s "$BASE/admin/events?conversationId=1311" -H "Authorization: Bearer $ADMIN" | jq
curl -s "$BASE/admin/jobs" -H "Authorization: Bearer $ADMIN" | jq
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.