Skip to main content

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)TypeWritten byRead by
webhook_config:{webhookId}hashcms-api (when an OA's config changes)webhook-go (read-only)
agent_mode:{lineOaId}:{userId}hashworker-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

FieldPurpose
lineOaIdParsed to int64 and attached to every queue payload; an unparsable value yields 0 rather than a crash
forwardWebhookUrlWhen non-empty, the raw body is forwarded to this URL
mboxEnabled"1" enables the human agent chat system
mboxBaseUrl, mboxApiToken, mboxAccountId, mboxInboxIdChatwoot credentials attached to the payload sent to the worker
mboxLineWebhookUrlDestination URL for messages forwarded while in agent mode
mboxTimeoutMinutes, mboxWarningMinutesTimeout and warning intervals (consumed by the worker)
mboxGreetingMessage, mboxWarningMessage, mboxEndMessage, mboxTimeoutMessageSystem messages sent to the user
mboxExitKeywords, mboxAgentKeywordsKeywords that trigger entering and leaving agent mode
mboxDepartmentPickerEnabled"1" requires the user to pick a department before handoff
mboxDepartmentPickerHeaderText, mboxDepartmentPickerGeneralLabel, mboxDepartmentsDepartment 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 lastActivity field, 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

FileFunctions
internal/cache/cache.goCache.HGetAll, Cache.HSet, Cache.Get/Set/Del/DelWildcard/Keys/HGet/IncrBy/Expire, setKeyName (namespace prefixing)
internal/line/service.goCalls HGetAll for webhook_config and agent_mode; buildMboxConfig maps the hash into a struct
internal/platform/redisx/redisx.goMulti-instance manager; this service uses the instance named "redis"
internal/config/api_load.goREDIS_URL, REDIS_NAMESPACE (default LINE_MANAGEMENT), REDIS_TTL
cmd/api/main.goBuilds cache.New(rdb, namespace, ttl) and injects it into server.Deps.Cache

Connections to Other Services

  • cms-api — the line-oa-management and auto-response modules 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 the line_webhook queue.
  • The same Redis instance also backs the rate limiter, but in a separate key space under throttle:app:*.
  • If Redis is not configured (REDIS_URL empty), deps.Cache is nil and the handler never spawns the pipeline. The endpoint still returns 200, but no processing takes place.