Skip to content

Chatwoot Wrapper — End-to-End Testing Guide

Complete guide to test the Chatwoot wrapper extension from zero to working conversations.

Prerequisites

  • Java 25+, Maven installed
  • Docker + Docker Compose installed
  • Ports available: 3000 (Chatwoot), 5433 (Postgres), 6380 (Redis), 8081 (Wrapper)

Phase 1: Start Chatwoot + Run Setup

bash
# From the repo root — start infrastructure
docker compose -f chatwoot-extension/docker/docker-compose.yml up -d

Wait ~30s for migration to complete. Verify all containers are up:

bash
docker compose -f chatwoot-extension/docker/docker-compose.yml ps

All services should show Up (except chatwoot-migrate which exits after completing).

Now run the automated setup script. It creates the SuperAdmin, PlatformApp token, and agent user in one go:

bash
bash chatwoot-extension/docker/setup.sh

On success you'll see output like:

=== Chatwoot Initial Setup ===
Step 1: Creating SuperAdmin user...
SuperAdmin (created): admin@metaone.local
Step 2: Creating PlatformApp for API access...
Platform token: qYdvVLWb7sgdJNfud7XuskmW
Platform API verified.
Step 3: Creating agent user for Application API...
Agent user ID: 2
Agent token: 8fXT4b8PL3Gv8s9sfkfJ7CrZ
=== Setup Complete ===

Save those values:

bash
export CW_PLATFORM_TOKEN=<platform token from Step 2>
export CW_AGENT_TOKEN=<agent token from Step 3>

What the setup script does:

  1. Creates a SuperAdmin user (for Chatwoot web admin access)
  2. Creates a PlatformApp and its token (the Platform API at /platform/api/v1/* requires a PlatformApp token, NOT a user access token)
  3. Creates an agent user via the Platform API (this user's access token is needed for the Application API: conversations, messages, contacts)

Phase 2: Build & Start the Wrapper

bash
# Build (from repo root)
mvn -pl chatwoot-extension clean package -DskipTests -q

# Make sure port 8081 is free
lsof -ti:8081 | xargs kill 2>/dev/null; true

# Start the wrapper
CHATWOOT_PLATFORM_TOKEN=$CW_PLATFORM_TOKEN \
  mvn -pl chatwoot-extension spring-boot:run

Open a new terminal for the remaining test commands (re-export the env vars there).

Verify the wrapper is running:

bash
curl -s http://localhost:8081/health | python3 -m json.tool

Expected:

json
{
    "status": "UP",
    "chatwoot": "reachable"
}

Phase 3: Test Widget Config (Account Provisioning)

This creates a Chatwoot account for your workspace on first access.

bash
curl -s -H "X-Workspace-Id: ws-demo" http://localhost:8081/widget/config | python3 -m json.tool

Expected:

json
{
    "chatwootBaseUrl": "http://localhost:3000",
    "identityValidationEnabled": false,
    "accountId": 1
}

What happened: The wrapper auto-provisioned a Chatwoot account for workspace ws-demo via the Platform API and stored the mapping in its H2 database.


Phase 4: Provision Agent into the Account

The wrapper created a Chatwoot account in Phase 3, but the capability endpoints (conversations, messages) use Chatwoot's Application API, which needs an agent user assigned to that account.

Step 4a: Get the account ID

bash
curl -s http://localhost:3000/platform/api/v1/accounts \
  -H "api_access_token: $CW_PLATFORM_TOKEN" | python3 -m json.tool

Note the account id (likely 1).

bash
export CW_ACCOUNT_ID=1  # adjust if different

Step 4b: Add the agent user to the account

The setup script already created an agent user. Now add them to the account:

bash
# Get agent user ID (printed by setup.sh, or find it)
export CW_AGENT_USER_ID=<agent user ID from setup.sh>

curl -s -X POST "http://localhost:3000/platform/api/v1/accounts/$CW_ACCOUNT_ID/account_users" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_PLATFORM_TOKEN" \
  -d "{
    \"user_id\": $CW_AGENT_USER_ID,
    \"role\": \"administrator\"
  }" | python3 -m json.tool

Step 4c: Verify the agent token works

bash
curl -s "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/conversations" \
  -H "api_access_token: $CW_AGENT_TOKEN" | python3 -m json.tool

Expected: {"data": {"meta": {...}, "payload": []}} (empty conversations list).


Phase 5: Test Conversations Against Chatwoot API

Test the full conversation lifecycle directly against Chatwoot's API.

Create an inbox (conversations need one)

bash
curl -s -X POST "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/inboxes" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_AGENT_TOKEN" \
  -d '{
    "name": "MetaOne Chat",
    "channel": {
      "type": "api",
      "webhook_url": "http://host.docker.internal:8081/webhooks/chatwoot"
    }
  }' | python3 -m json.tool
bash
export CW_INBOX_ID=<inbox id from response>

Create a contact

bash
curl -s -X POST "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/contacts" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_AGENT_TOKEN" \
  -d '{
    "name": "Test User",
    "email": "testuser@example.com"
  }' | python3 -m json.tool
bash
export CW_CONTACT_ID=<contact id from response>

Create a conversation

bash
curl -s -X POST "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/conversations" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_AGENT_TOKEN" \
  -d "{
    \"source_id\": \"metaone-test-$(date +%s)\",
    \"inbox_id\": $CW_INBOX_ID,
    \"contact_id\": $CW_CONTACT_ID,
    \"additional_attributes\": {\"name\": \"Test Conversation\"}
  }" | python3 -m json.tool
bash
export CW_CONVERSATION_ID=<conversation id from response>

Send a message

bash
curl -s -X POST "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/conversations/$CW_CONVERSATION_ID/messages" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_AGENT_TOKEN" \
  -d '{
    "content": "Hello from MetaOne testing!",
    "message_type": "outgoing"
  }' | python3 -m json.tool

List messages

bash
curl -s "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/conversations/$CW_CONVERSATION_ID/messages" \
  -H "api_access_token: $CW_AGENT_TOKEN" | python3 -m json.tool

List conversations

bash
curl -s "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/conversations" \
  -H "api_access_token: $CW_AGENT_TOKEN" | python3 -m json.tool

Phase 6: Test Widget Identity Token

bash
# Without identity validation token (returns empty hash)
curl -s http://localhost:8081/widget/identity-token \
  -H "X-Workspace-Id: ws-demo" \
  -H "X-Actor-Id: user-42" | python3 -m json.tool

Expected:

json
{
    "identifier": "user-42",
    "identityHash": ""
}

To test HMAC generation, restart the wrapper with a validation token:

bash
# Stop wrapper (Ctrl+C), then restart with:
CHATWOOT_PLATFORM_TOKEN=$CW_PLATFORM_TOKEN \
CHATWOOT_IDENTITY_VALIDATION_TOKEN=my-secret-key \
  mvn -pl chatwoot-extension spring-boot:run

Then:

bash
curl -s http://localhost:8081/widget/identity-token \
  -H "X-Workspace-Id: ws-demo" \
  -H "X-Actor-Id: user-42" | python3 -m json.tool

Expected: a non-empty identityHash (HMAC-SHA256 hex string).


Phase 7: Test Webhook Bridge (Chatwoot → Wrapper)

Register a webhook in Chatwoot

bash
curl -s -X POST "http://localhost:3000/api/v1/accounts/$CW_ACCOUNT_ID/webhooks" \
  -H "Content-Type: application/json" \
  -H "api_access_token: $CW_AGENT_TOKEN" \
  -d '{
    "url": "http://host.docker.internal:8081/webhooks/chatwoot",
    "subscriptions": ["message_created", "conversation_created", "conversation_status_changed"]
  }' | python3 -m json.tool

host.docker.internal lets Docker containers reach your Mac's localhost.

Trigger a webhook event

Send a message (Phase 5) and check the wrapper terminal output.

You should see: Received Chatwoot webhook event: message_created

The webhook tries to forward events to MetaOne Core at http://localhost:8080/api. If Core isn't running, you'll see a log error. That's expected.


Phase 8: Test Event Forwarding (MetaOne → Wrapper)

bash
curl -s -X POST http://localhost:8081/events \
  -H "Content-Type: application/json" \
  -H "X-Workspace-Id: ws-demo" \
  -d '{
    "eventKey": "platform.notification",
    "payload": {
      "channel": "general",
      "message": "New deal closed: Acme Corp $50k",
      "sender": "crm-bot"
    }
  }'

Check wrapper logs for: Forwarding notification to Chatwoot: [crm-bot] New deal closed...

Note: The actual message delivery to a Chatwoot conversation is a TODO (needs channel-to-conversation mapping). The event is received and logged.


Quick Reference: All Endpoints

EndpointMethodHeadersPurpose
/healthGETHealth check
/widget/configGETX-Workspace-IdWidget SDK config
/widget/identity-tokenGETX-Workspace-Id, X-Actor-IdHMAC identity hash
/capabilities/chat.conversation.createPOSTX-Workspace-IdCreate conversation
/capabilities/chat.conversation.getPOSTX-Workspace-IdGet conversation by ID
/capabilities/chat.conversation.listPOSTX-Workspace-IdList conversations
/capabilities/chat.message.sendPOSTX-Workspace-IdSend a message
/capabilities/chat.message.listPOSTX-Workspace-IdList messages
/capabilities/chat.member.addPOSTX-Workspace-IdAdd participant
/capabilities/chat.member.removePOSTX-Workspace-IdRemove participant
/webhooks/chatwootPOSTChatwoot webhook receiver
/eventsPOSTX-Workspace-IdMetaOne event receiver

Chatwoot Token Types

TokenCreated fromUsed forAPI prefix
PlatformApp tokenPlatformApp modelPlatform API (accounts, users)/platform/api/v1/*
User access tokenUser modelApplication API (conversations, messages)/api/v1/*
SuperAdmin tokenSuperAdmin userChatwoot admin web UIN/A (web login)

The Platform API and Application API use the same header (api_access_token) but require different token types. This is the most common source of 401 errors.

Environment Variables

VariableDefaultPurpose
CHATWOOT_BASE_URLhttp://localhost:3000Chatwoot instance URL
CHATWOOT_PLATFORM_TOKEN(required)PlatformApp API token
CHATWOOT_WEBHOOK_SECRET(empty)Webhook signature validation
CHATWOOT_IDENTITY_VALIDATION_TOKEN(empty)HMAC key for widget identity
METAONE_CORE_URLhttp://localhost:8080/apiMetaOne Core base URL

Cleanup

bash
# Stop wrapper: Ctrl+C in that terminal, or:
lsof -ti:8081 | xargs kill

# Stop Chatwoot stack (preserves data in Docker volumes):
docker compose -f chatwoot-extension/docker/docker-compose.yml down

# Full cleanup (destroys all data):
docker compose -f chatwoot-extension/docker/docker-compose.yml down -v

Known Issues

  1. Wrapper capability endpoints return 401provisionWorkspace() creates a Chatwoot account but doesn't provision an agent user into it. The fix: after creating the account, also create a user via POST /platform/api/v1/users, add them to the account via POST /platform/api/v1/accounts/{id}/account_users, and store that user's access_token as the agentToken in the workspace mapping.

  2. Event forwarding is incomplete — The EventController receives platform.notification events but doesn't yet map channel names to Chatwoot conversation IDs.

  3. Conversation create needs inbox_id — Chatwoot requires an inbox_id to create conversations. The wrapper should auto-create an API inbox per workspace during provisioning and store that ID.

MetaOne Platform Documentation