Redis Cache for Webhook Config & Agent Mode
Overview
On the LINE event path, the webhook service never touches PostgreSQL. Every routing decision comes from just two Redis keys. The design is deliberate: this endpoint must respond to LINE as fast as possible and carries the highest traffic volume in the system.
| Key (before prefixing) | Type | Written by | Read by |
|---|---|---|---|
webhook_config:{webhookId} | hash | cms-api (when an OA's config changes) | webhook-go (read-only) |
agent_mode:{lineOaId}:{userId} | hash | worker-go (session start/end) and webhook-go (lastActivity only) | webhook-go |
Every key is automatically prefixed with REDIS_NAMESPACE (default LINE_MANAGEMENT:) by
internal/cache.Cache. The code refers to short names, but the actual Redis key is
LINE_MANAGEMENT:webhook_config:xxxx.
Business Flow
Fields the code actually reads from webhook_config
| Field | Purpose |
|---|---|
lineOaId | Parsed to int64 and attached to every queue payload; an unparsable value yields 0 rather than a crash |
forwardWebhookUrl | When non-empty, the raw body is forwarded to this URL |
mboxEnabled | "1" enables the human agent chat system |
mboxBaseUrl, mboxApiToken, mboxAccountId, mboxInboxId | Chatwoot credentials attached to the payload sent to the worker |
mboxLineWebhookUrl | Destination URL for messages forwarded while in agent mode |
mboxTimeoutMinutes, mboxWarningMinutes | Timeout and warning intervals (consumed by the worker) |
mboxGreetingMessage, mboxWarningMessage, mboxEndMessage, mboxTimeoutMessage | System messages sent to the user |
mboxExitKeywords, mboxAgentKeywords | Keywords that trigger entering and leaving agent mode |
mboxDepartmentPickerEnabled | "1" requires the user to pick a department before handoff |
mboxDepartmentPickerHeaderText, mboxDepartmentPickerGeneralLabel, mboxDepartments | Department picker button data; mboxDepartments is stored as a JSON string |
All values are strings because they come from a Redis hash, so the code compares directly
against "1" rather than using booleans.
Cache miss behaviour
When HGETALL returns an empty map, the service treats it as "no config", publishes the whole
payload to the line_webhook queue, and stops. This is not an error and there is no database
fallback — worker-go looks the config up from the database on its own.
Redis error behaviour
The service logs processLine: HGETALL webhook_config failed and returns immediately. The
message is silently dropped with no retry and no dead letter queue, because the 200 response
has already been sent back to LINE. This is important to know when investigating missing
messages.
agent_mode and activity tracking
- The presence of the key (a non-empty hash) means this user is currently talking to a human agent, so the bot must stay silent.
- webhook-go writes only the
lastActivityfield, an epoch timestamp in milliseconds stored as a string, on every incoming message. It deliberately sets no TTL — session expiry is worker-go's responsibility. - Creating and deleting this key is entirely worker-go's job; webhook-go only touches
lastActivity.
Key Files & Functions
| File | Functions |
|---|---|
internal/cache/cache.go | Cache.HGetAll, Cache.HSet, Cache.Get/Set/Del/DelWildcard/Keys/HGet/IncrBy/Expire, setKeyName (namespace prefixing) |
internal/line/service.go | Calls HGetAll for webhook_config and agent_mode; buildMboxConfig maps the hash into a struct |
internal/platform/redisx/redisx.go | Multi-instance manager; this service uses the instance named "redis" |
internal/config/api_load.go | REDIS_URL, REDIS_NAMESPACE (default LINE_MANAGEMENT), REDIS_TTL |
cmd/api/main.go | Builds cache.New(rdb, namespace, ttl) and injects it into server.Deps.Cache |
Connections to Other Services
- cms-api — the
line-oa-managementandauto-responsemodules warm this cache. If the cache is missing or out of sync, the webhook falls back to default mode and only pushes events into the queue. - worker-go — owns the full lifecycle of
agent_mode:*, creating it on a successful handoff and deleting it on exit or timeout, and consumes theline_webhookqueue. - The same Redis instance also backs the rate limiter, but in a separate key space under
throttle:app:*. - If Redis is not configured (
REDIS_URLempty),deps.Cacheis nil and the handler never spawns the pipeline. The endpoint still returns200, but no processing takes place.