Skip to main content

Dialer Worker

The Dialer Worker is a Go-based service that executes outbound auto-dialer campaigns. It polls the Laravel API for active campaigns, rate-limits call initiation, and initiates calls through Cloudonix.

Purpose​

The worker handles:

  • Polling for active campaigns every 10 seconds
  • Fetching pending destinations from Laravel
  • Enforcing CAC (Concurrent Active Calls) and CPS (Calls Per Second) limits
  • Initiating calls via the Laravel API
  • Receiving CDR webhooks from Laravel
  • Managing retry queues in Redis
  • Selecting Caller IDs from pools

Architecture​

Build and Run​

The worker starts automatically with the full OPBX stack:

docker compose up -d

Local Development​

cd dialer-worker

cp .env.example .env
# Edit .env with your configuration

make deps
make build
make run

Makefile Targets​

TargetDescription
make buildBuild the worker binary
make runRun with go run
make testRun tests
make fmtFormat Go code
make lintRun golangci-lint
make docker-buildBuild Docker image
make docker-runRun Docker container with .env
make healthCheck worker health endpoint

Environment Variables​

VariableDefaultRequiredDescription
LARAVEL_API_URLhttp://localhost:8000YesLaravel API base URL
LARAVEL_API_TOKEN—YesBearer token for Laravel API
REDIS_HOSTlocalhostYesRedis host
REDIS_PORT6379YesRedis port
REDIS_PASSWORD—NoRedis password
REDIS_DB0NoRedis database
WORKER_IDworker-1YesUnique worker identifier
POLL_INTERVAL10sNoCampaign polling interval
WEBHOOK_PORT8081YesHTTP server port for CDR webhooks and health checks
note

The worker exposes a single HTTP server on WEBHOOK_PORT. Health checks are available at GET /webhooks/health on this port.

| WEBHOOK_SECRET | — | No | HMAC-SHA256 secret for CDR webhooks |

note

Laravel uses a dedicated dialer Redis connection with no key prefix for all keys shared with the Go worker. This ensures both services read and write the same keys.

Campaign Lifecycle Overview​

  1. Worker polls GET /v1/dialer/worker/campaigns/active
  2. For each active campaign, it registers CAC/CPS limits
  3. It fetches pending destinations: GET /campaigns/{id}/destinations/pending
  4. For each destination, it acquires a Redis lock, checks CAC, selects a Caller ID, and calls POST /calls/initiate
  5. On successful initiation, it increments the active call counter
  6. When Laravel receives a CDR webhook, it decrements the counter and the worker receives a relayed CDR event
  7. The worker updates call status and schedules retries if needed

Redis Key Patterns​

Key PatternPurpose
dialer:cac:{id}:activeActive call counter per campaign
dialer:call:{session}Call state hash
dialer:lock:{key}Distributed locks
dialer:retry:{campaign}Retry sorted set
dialer:worker:{id}Worker heartbeat
dialer:idem:{key}Webhook idempotency

Horizontal Scaling​

Multiple worker instances can run concurrently:

docker compose up -d --scale dialer-worker=3

Each instance polls independently and uses Redis for distributed locking and shared state.

Troubleshooting​

IssueSolution
Worker not dialingCheck LARAVEL_API_URL and LARAVEL_API_TOKEN; verify campaign status is ACTIVE
Health check unreachableCheck WEBHOOK_PORT and query GET /webhooks/health on that port
CAC counter driftThe worker reconciles the counter against live call state on each poll cycle
Webhook auth failuresEnsure WEBHOOK_SECRET matches the value used by Laravel
High memory usageReduce POLL_INTERVAL or scale horizontally