Tagon
Logistics infrastructure APIs so African businesses can automate delivery without rebuilding ops from scratch.
Delivery operations fragment across chat channels, payment providers, and internal dashboards. Without one API plane, every integration becomes a one-off webhook and a silent failure mode.
- FastAPI
- SQLAlchemy
- PostgreSQL
- Redis
- Celery
- Twilio WhatsApp
- Telegram
- Flutterwave
› What I built
- Shipped a FastAPI service with async SQLAlchemy, Alembic migrations, and a warm DB pool so cold starts do not stall the first request
- Wired WhatsApp, Telegram, and Flutterwave webhooks behind one /api/v1 surface with health checks that surface which integrations are live
- Queued background work on Celery with Redis as broker — message delivery and payment side-effects stay off the request path
- Separated web routes, API routers, repositories, and services so channel adapters can grow without coupling to domain logic
- Configured JWT auth, Redis backoff, and Mangum-ready entrypoints for flexible deployment targets
Twilio webhook
Telegram
Bot webhook
Flutterwave
Payment webhook
Tagon API
FastAPI /api/v1
Redis
Broker + cache
Celery workers
Async tasks
PostgreSQL
Source of truth
- wa → api
- tg → api
- fw → api
- api → redis (enqueue)
- redis → worker
- api → db
- worker → db
› Why I built it that way
One API plane for channels, payments, and workers
- Context
- Logistics products usually grow chat bots, payment callbacks, and admin APIs as separate apps. That multiplies auth, logging, and failure handling.
- Choice
- Kept Tagon as one FastAPI application with versioned API routes, channel webhooks, and Celery workers sharing the same config and data model.
- Because
- A single contract for health, auth, and schema means a new channel is an adapter — not a new service to operate.
The hard part is not accepting a webhook; it is knowing which rails are configured when something fails at 2am. Tagon's health endpoint reports database, auth, WhatsApp, Telegram, Flutterwave, and Redis state in one place. Background work is mandatory for message and payment side-effects, so Celery is treated as part of the product, not an optional queue.
Options considered
| Separate microservices per channel | Monolith without workers | One API + Celery workers | |
|---|---|---|---|
| Ops surface | Many deploys and health checks | One deploy, blocked requests | One deploy, async side-effects |
| Adding Telegram or WhatsApp | New service + networking | Blocks the request thread | Adapter + queue task |
| Payment webhooks | Duplicated auth and logging | Slow responses under load | Shared schema, async settle |
| Chosen | No | No | Yes |