What is Hermes
Hermes is the integration middleware that connects our applications to external services such as Zoho and Kraken.
Hermes acts as a bridge between your applications and third parties. It handles OAuth authentication, forwards REST and GraphQL requests, and routes inbound webhooks to subscribed apps.
What is it for?
- Authenticated proxy to Zoho CRM, Books, and WorkDrive
- GraphQL relay to Kraken with no embedded queries
- Inbound Zoho webhook reception and HMAC-signed delivery
- Centralised OAuth tokens and HTTP retries
Production base URL:https://hermes.oees.tech/api/v1
For deployment, environment variables, and operations, see the repository: https://github.com/oees/hermes
Architecture
Hermes exposes a FastAPI API and a Celery worker sharing PostgreSQL for credentials, API clients, and webhook subscriptions.
Outgoing calls
Your App ──(X-API-Key)──▶ Hermes ──(OAuth / JWT)──▶ Third party
│
▼
┌──────────────────────┐
│ PostgreSQL (DO) │
└──────────────────────┘Inbound webhooks
Zoho ──webhook──▶ Hermes ──route by subscription_name──▶ Your App(s)
│
▼
Celery worker
│
▼
POST + HMAC signatureMain components
- FastAPI API — HTTP endpoints, CORS, API key validation
- Celery worker — Asynchronous webhook delivery with retries
- PostgreSQL — OAuth credentials, API clients, subscriptions
- Integrations — REST/GraphQL clients in app/integrations/
Authentication
Protected endpoints require a valid API key. Hermes handles third-party authentication separately.
API key (caller → Hermes)
Include this header on every request to protected endpoints:
X-API-Key: hms_live_{32_hex}Generate an API key
python -m app.cli.commands create-api-key my-appErrors
An invalid or missing API key returns HTTP 401 — Invalid or missing API key.
Hermes → third parties
- Zoho CRM, Books, WorkDrive: OAuth2 refresh token → access token
- Kraken: client credentials → JWT via auth server
Integrations
Hermes supports four integration families. Each exposes setup and proxy (or relay) endpoints under /api/v1/.
Zoho CRM
REST proxy to API v8 and read-only COQL queries.
POST /zoho/setup — store OAuth tokensPOST /zoho/proxy — REST proxy (GET/POST/PUT/PATCH/DELETE)POST /zoho/query — COQL queries
Zoho Books
Accounting REST proxy; Hermes injects organization_id automatically.
POST /zoho-books/setup — bootstrap with refresh_tokenPOST /zoho-books/proxy — Books v3 REST proxy
Zoho WorkDrive
REST proxy, downloads, and file uploads (max 250 MB).
POST /zoho-workdrive/setup — bootstrap with refresh_tokenPOST /zoho-workdrive/proxy — WorkDrive REST proxyPOST /zoho-workdrive/upload — multipart upload
Kraken
Pure GraphQL relay: the caller sends query, variables, and operationName.
POST /kraken/graphql — relay with no embedded queries
API reference
All paths are relative to https://hermes.oees.tech/api/v1. OpenAPI (/docs) is only available when APP_DEBUG=true.
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /health | No | Health check (DB + zoho_configured) |
POST | /zoho/setup | Yes | Configure OAuth CRM tokens |
POST | /zoho/proxy | Yes | Proxy to Zoho CRM API |
POST | /zoho/query | Yes | COQL read-only queries |
POST | /zoho-books/setup | Yes | Configure Zoho Books OAuth |
POST | /zoho-books/proxy | Yes | Proxy to Zoho Books API |
POST | /zoho-workdrive/setup | Yes | Configure WorkDrive OAuth |
POST | /zoho-workdrive/proxy | Yes | Proxy to WorkDrive API |
POST | /zoho-workdrive/upload | Yes | Upload file (multipart, max 250 MB) |
POST | /kraken/graphql | Yes | Relay GraphQL to Kraken |
POST | /webhooks/zoho | No | Zoho webhook receiver |
POST | /webhooks/webhook-subscriptions | Yes | Create webhook subscription |
DELETE | /webhooks/webhook-subscriptions/{id} | Yes | Delete subscription (owner only) |
Webhooks
Hermes receives webhooks from Zoho and asynchronously forwards them to subscribed apps, signed with HMAC-SHA256.
Flow
- Zoho sends POST /api/v1/webhooks/zoho with subscription_name in the body
- Hermes finds active subscriptions whose name matches
- Celery enqueues delivery to each target_url
- Your app verifies X-Hermes-Signature with the subscription secret
Routing by subscription_name
Zoho may send one name or several. Supported formats:
Simple string: "my-app"Bracketed string: "[app-a, app-b]"JSON array: ["app-a", "app-b"]
Create subscription
The name field is the routing key. module and events are metadata only; Hermes does not filter inbound webhooks by them.
Payload your app receives
- integration, module, event, data, timestamp, subscription_id
- Headers: X-Hermes-Signature, X-Hermes-Subscription-Id
Verify signature
Compare the HMAC-SHA256 of the JSON body with the header:
X-Hermes-Signature: sha256={hex}Retries
Up to 5 retries with exponential backoff (10s, 20s, 40s, 80s, 160s).
Quick start
Minimal examples to verify connectivity and start integrating. Replace hms_live_xxx with your API key.
Health check
curl https://hermes.oees.tech/api/v1/healthZoho CRM proxy
curl -X POST https://hermes.oees.tech/api/v1/zoho/proxy \
-H "X-API-Key: hms_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"method": "GET",
"endpoint": "/Leads/12345"
}'Create webhook subscription
curl -X POST https://hermes.oees.tech/api/v1/webhooks/webhook-subscriptions \
-H "X-API-Key: hms_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"integration": "zoho",
"module": "Leads",
"events": ["create", "edit"],
"target_url": "https://my-app.com/webhooks"
}'