External Integrations
Purpose
Reference table of every external service HUPH talks to: purpose, env vars, and gotchas. Use this as the first stop when debugging an integration issue or when adding a new one.
Prerequisites
- Architecture overview — know where each service fits
.env.exampleandCREDENTIALS.md(for live keys)
Integration matrix
| Integration | Purpose | Env vars | Status | Gotchas |
|---|---|---|---|---|
| 360dialog | WhatsApp Business API — webhook receiver + outbound send | D360_API_KEY, D360_WEBHOOK_SECRET, D360_API_BASE_URL |
Active — only channel | Current tier does NOT expose App Secret for HMAC webhook signing; D360_WEBHOOK_SECRET is for hub.verify_token one-time subscription bootstrap only |
| Telegram Bot API | Telegram channel | TELEGRAM_BOT_TOKEN |
DELETED Apr 8 2026 | Code removed from apps/api/src/routes/telegram.ts; do not reintroduce without product approval |
| Anthropic API (Claude Haiku) | LLM used by apps/api for intent router Layer 3 fallback + eval judging + by Dify for chat generation |
ANTHROPIC_API_KEY, ANTHROPIC_MODEL |
Active | Direct API, NOT AWS Bedrock. Model ID: claude-haiku-4-5 (no date suffix) to match Phase 1 fallback. Dify's workflow references the same model |
| Dify (self-hosted) | Primary AI platform — chat completion (/v1/chat-messages), annotation reply (/v1/apps/annotations), KB management (/v1/datasets/*), workflow orchestration |
DIFY_API_URL, DIFY_APP_API_KEY, DIFY_KB_API_URL, DIFY_KB_API_KEY, DIFY_DATASET_ID, DIFY_INTENT_DATASET_ID |
Active (primary AI layer since early Apr 2026) | FAQ annotations match in ~300ms (bypasses LLM). Stack = ~8 containers. OOM'd on Apr 8 when dify-beat hit 256m during flask upgrade-db — memory limits bumped. Lives in docker-compose.dify.yml, separate from main compose |
| OpenAI (embeddings) | Dify uses OpenAI's embedding API when ingesting + querying KB documents | OPENAI_API_KEY |
Active (via Dify) | apps/api does NOT call OpenAI directly — only the Dify stack does. Key is threaded through docker-compose.dify.yml env |
| Milvus | Vector DB used by Dify KB | (Dify-internal URL) | Active — primary vector DB | Brought up via docker-compose.milvus.yml, separate from main compose. Not accessed directly by apps/api — all retrieval goes through Dify's /v1/datasets/* API |
| Qdrant | Formerly the vector DB | — | REMOVED late Mar / early Apr 2026 | docker-compose.yml line 33 explicitly comments "Qdrant removed — replaced by Milvus". qdrant_data volume kept as 30-day backup per line 102. Do NOT try to reintroduce |
| Postgres | Primary DB (conversations, leads, notifications, admin_users, clusters, audit_log) | DATABASE_URL, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT |
Active | Host port 5433 (container 5432) |
| Valkey (Redis-compatible) | Session cache, short-lived state | REDIS_URL |
Active | Host port 49379 (container 6379). Service name huph-valkey. Valkey is API-compatible with Redis — same clients work. Shared across val.id infra via huph_huph-infra external network |
| Phoenix | OpenTelemetry trace collection from apps/api |
port 6006 (internal) | Active | Local dev only; no prod auth currently |
| Langfuse | LLM observability (prompts, latencies, cost) | LANGFUSE_URL, LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY |
Active | ClickHouse backend had self-OOM loop fixed Apr 8 (see operations/clickhouse-oom.en.md). Dify tracing integration blocked by missing Langfuse SDK in Dify 1.13.3 — verify current Dify version |
| mem0 (optional) | Per-user long-term memory | MEM0_PG_URL, MEM0_NEO4J_URL, MEM0_NEO4J_USER, MEM0_NEO4J_PASSWORD |
Active (if enabled) | Brought up via docker-compose.mem0.yml. Neo4j backend holds PII — must use dedicated HUPH instance, never share (per feedback_dedicated_infra memory) |
| Neo4j (mem0 backend) | Graph DB for mem0 memory | same as mem0 env vars | Active (when mem0 enabled) | Port 48687 bolt / 48474 http in local dev (see CREDENTIALS.md) |
| UPH Admission API (Laravel) | Legacy student data enrichment — cross-reference to one.uph.edu |
UPH_API_URL, UPH_API_KEY |
Deferred | Integration deferred per project_laravel_enrichment_deferred memory. Do NOT auto-suggest as next candidate |
| Midtrans | Payment gateway for form purchases | MIDTRANS_CLIENT_KEY, MIDTRANS_SERVER_KEY |
Deferred | Not yet integrated. Waiting on product decision |
Key environment variable reference
# Anthropic (used by apps/api + Dify)
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
# OpenAI (embeddings — used by Dify, passed through from root .env)
OPENAI_API_KEY=sk-...
# 360dialog WhatsApp
D360_API_KEY=...
D360_WEBHOOK_SECRET=...
D360_API_BASE_URL=https://waba-v2.360dialog.io
# Dify (primary AI layer)
DIFY_API_URL=http://huph-dify-api:5001 # internal URL
DIFY_APP_API_KEY=... # chat-messages app token
DIFY_KB_API_URL=http://huph-dify-api:5001
DIFY_KB_API_KEY=... # KB management token
DIFY_DATASET_ID=<uuid> # main KB dataset
DIFY_INTENT_DATASET_ID=<uuid> # optional intent dataset
# Database (Postgres)
DATABASE_URL=postgresql://huph:...@postgres:5432/huph # internal
# Or from host: postgresql://huph:...@localhost:5433/huph
DB_USER=huph
DB_PASSWORD=...
DB_NAME=huph
DB_PORT=5433
# Valkey (Redis-compatible cache)
REDIS_URL=redis://huph-valkey:6379 # internal
# Or from host: redis://localhost:49379
# NextAuth (must be in BOTH admin AND api container env)
NEXTAUTH_SECRET=...
NEXTAUTH_URL=https://admin.huph.val.id
JWT_SECRET=...
# mem0 (optional)
MEM0_PG_URL=postgresql://mem0:...@localhost:48432/mem0
MEM0_NEO4J_URL=bolt://localhost:48687
MEM0_NEO4J_USER=neo4j
MEM0_NEO4J_PASSWORD=...
# Observability
LANGFUSE_URL=https://langfuse.huph.val.id
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
# UPH legacy (deferred)
UPH_API_URL=https://admission.uph.edu/api/v1
UPH_API_KEY=...
# Feature flags
LEAD_CAPTURE_ENABLED=true
TEAM_OWNERSHIP_ENABLED=true
ESCALATION_ROUTING_ENABLED=true
API_AUTH_MODE=disabled # disabled | warn | enforce
See .env.example for the full list.
Infrastructure rule: dedicated HUPH stacks
From memory feedback_dedicated_infra: HUPH must use dedicated
infrastructure, never share tools with other server projects.
Applies especially to:
- mem0 instance (contains PII)
- Langfuse instance (contains LLM conversation history)
- Postgres (HUPH-only data)
- Redis (HUPH-only session cache)
Do not suggest sharing these with other projects on the same server.
Gotchas (cross-integration)
- Docker internal URLs ≠ host URLs. Inside containers:
postgres:5432,huph-valkey:6379,huph-dify-api:5001. From host:localhost:5433,localhost:49379,localhost:5001. Mixing them causes "connection refused" errors. - docker-compose.yml needs explicit env passthrough. Writing
to
.envis not enough — the API container reads from theenvironment:block. Every new env var needs a${VAR:-default}line indocker-compose.yml. - 360dialog tier limitations. Current tier does not expose an
App Secret → no HMAC webhook signing possible. Webhook auth
relies on network isolation (port 3101 bound to 127.0.0.1) +
hub.verify_tokenone-time subscription. - Dify stack can OOM. 7 containers (dify-api, worker, web,
sandbox, plugin-daemon, ssrf-proxy, beat) died at once on Apr 8
because
huph-dify-beathit 256MB limit duringflask upgrade-db. Memory limits bumped:dify-beat256m→512m,dify-worker512m→1g,dify-plugin-daemon2g→3g,clickhouse2g→3g. - NextAuth secret must be in both admin AND api containers.
Realtime Socket.io auth uses
X-Forwarded-SessionJWE — if the API can't decode it, clients stuck Offline. - WhatsApp is the only active channel. Telegram and Web were deleted Apr 8. Code references to them in old specs/memories are historical.
- Laravel integration is deferred. Do not auto-suggest UPH
legacy admission API integration as a next candidate phase. See
memory
project_laravel_enrichment_deferred.