Baqo is a secure, extensible bi-directional webhook relay service β handling both inbound webhooks from third-party providers and outbound dispatch events to your own systems or partner APIs.
Follow these steps to get Baqo running locally with Docker:
git clone https://github.com/YOUR_ORG_OR_USERNAME/baqo-deploy.git
cd baqo-deployThis is the standalone deployment repo. It includes Docker setup and configuration.
Copy the example .env file and fill in required values:
cp .env.example .envSet secrets like POSTGRES_USER, STRIPE_SECRET, etc.
Edit config/source_verification.yaml to define your webhook sources:
sources:
stripe:
type: stripe
secret: ${STRIPE_SECRET}
event_id_path: id
handler_url: http://host.docker.internal:9000/
verify: trueBaqo can also forward or dispatch events to external systems.
Define outbound destinations in the same YAML file:
destinations:
meetball_success:
type: none
handler_url: http://host.docker.internal:9003/success
verify: falseYou can then POST to:
POST /dispatch/meetball_success
to trigger an outbound delivery.
Once your .env and YAML config are set up, run:
docker compose up --buildThis will:
- Start Postgres, Redis, the Baqo app, and Celery worker
- Run migrations automatically
- Start accepting webhooks at
http://localhost:8000/webhooks/{source} - Start dispatching outbound events at
http://localhost:8000/dispatch/{destination}
Inbound test:
curl -X POST http://localhost:8000/webhooks/stripe -H "Content-Type: application/json" -H "stripe-signature: your-generated-signature" -d '{"id": "evt_test_001", "data": {"object": {"id": "pi_001"}}}'Outbound test:
curl -X POST http://localhost:8000/dispatch/meetball_success -H "X-BAQO-KEY: secret123" -H "Content-Type: application/json" -d '{"event": "user.checkin", "data": {"id": 42}}'+-------------+ +--------+ +-------------+ +--------------+ +------------------+
| 3rd-party | --> | Baqo | --> | Postgres | --> | Celery | --> | Your App Handler |
| service | | API | | (Event Log) | | Worker Queue | | (HTTP Receiver) |
+-------------+ +--------+ +-------------+ +--------------+ +------------------+
| | | | |
| POST /webhooks | verify signature | log + deduplicate | enqueue delivery | POST payload
+-----------+ +--------+ +-------------+ +--------------+ +-------------------+
| Your App | --> | Baqo | --> | Postgres | --> | Celery | --> | External Endpoint |
| (Client) | | API | | (Event Log) | | Worker Queue | | (Receiver System) |
+-----------+ +--------+ +-------------+ +--------------+ +-------------------+
| | | | |
| POST /dispatch | create event | log event | enqueue delivery | POST payload
- β Webhook Verification β HMAC or custom plugin-based verification
- π Retry Logic β Smart backoff for transient errors (Celery-powered)
- π§± Deduplication β Prevent reprocessing via event_id
- π API Key Protection β Secure outbound endpoints
- π Event Logging & Observability β All attempts logged in Postgres
- βοΈ Extensible β Add new verification or delivery strategies easily
Baqo uses a YAML file mounted at /app/config/source_verification.yaml.
sources:
stripe:
type: stripe
secret: ${STRIPE_SECRET}
event_id_path: id
handler_url: http://your-backend.com/stripe-events
verify: true
paystack:
type: paystack
secret: ${PAYSTACK_SECRET}
event_id_path: data.id
handler_url: http://your-backend.com/paystack-handler
verify: true
destinations:
meetball_success:
type: none
handler_url: http://host.docker.internal:9003/success
verify: false| Field | Description |
|---|---|
type |
Plugin type (for verification or categorization) |
secret |
Secret key for signature verification (inbound only) |
event_id_path |
JSON path to event ID for deduplication |
handler_url |
Target URL for forwarding or dispatch |
verify |
Whether to verify TLS/SSL (disable for trusted LANs) |
| Variable | Purpose |
|---|---|
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB |
Database credentials |
CELERY_BROKER_URL |
Redis broker for Celery |
CELERY_RESULT_BACKEND |
Redis backend for Celery |
CELERY_MAX_RETRIES |
Max delivery retries |
CELERY_BACKOFF_BASE_DELAY |
Base delay for retry backoff |
BAQO_API_KEY |
Protects outbound /dispatch/* endpoints |
LOG_LEVEL, LOG_FORMAT, LOG_DATE_FORMAT |
Logging settings |
RUNNING_IN_DOCKER |
Should remain true for containerized deployments |
| Direction | Endpoint | Verification | Deduplication | Typical Use |
|---|---|---|---|---|
| Inbound | /webhooks/{source} |
Optional (Stripe, Paystack, etc.) | β Yes | Receive events from third parties |
| Outbound | /dispatch/{destination} |
API Key | β No | Send events to partner systems |
Each event record includes:
last_status_codeβ last HTTP response code receivedlast_response_excerptβ truncated text from the last responselast_attempt_atβ last delivery attempt timestamplast_errorβ latest known failure reason
This allows you to monitor event delivery health and debug failures easily.
You can add your own signature logic via custom_verifications/:
# app/custom_verifications/mycompany.py
VERIFICATION_TYPE = "mycompany_signature"
from starlette.datastructures import Headers
async def verify_signature(secret: str, headers: Headers, body_str: str):
signature = headers.get("x-mycompany-signature")
if not signature or signature != secret:
raise Exception("Invalid MyCompany signature")Then reference it in YAML:
sources:
mycompany:
type: mycompany_signature
secret: ${MYCOMPANY_SECRET}
event_id_path: id
handler_url: http://your-app.com/handler
verify: trueSend a dispatch event manually:
curl -X POST http://localhost:8000/dispatch/meetball_success -H "X-BAQO-KEY: secret123" -H "Content-Type: application/json" -d '{"event": "order.created", "data": {"id": "12345"}}'Follow app logs:
docker compose logs -f appFollow worker logs:
docker compose logs -f workerQuery events directly in Postgres to view retry history and results.
Baqo v0.2.0 β now with full outbound dispatching, improved observability, and smarter retries! π