Webhooks
Athena receives two categories of inbound webhooks: RevenueCat (subscription billing events) and media generation (Fal.ai async results). Your server only needs RevenueCat if your workspace uses the credit billing system.
RevenueCat Webhooks
Setup
Configure RevenueCat to send webhooks to:
POST https://athena-dev-api.leanscale.com/webhooks/{workspace_id}/revenuecatIn Workspace Settings → Subscription Providers, copy the RevenueCat Webhook
URL and set the Webhook Secret. RevenueCat sends the secret as a bearer token
in the Authorization header. Athena validates it with a constant-time
comparison.
Also configure the RevenueCat Project ID and Secret REST API Key in the same settings panel. Those values are used for RevenueCat v2 live subscription lookups after sign-in transfers, manual billing refreshes, and plan-change previews. Athena never needs the public SDK key on the server.
| Athena field | RevenueCat value | Used for |
|---|---|---|
| Webhook URL | Athena-generated URL | RevenueCat webhook delivery |
| Webhook Secret | RevenueCat webhook bearer secret | Verifying inbound webhooks |
| Project ID | RevenueCat project ID, e.g. proj... | v2 /projects/{project_id}/customers/{customer_id}/subscriptions lookups |
| Secret REST API Key | RevenueCat secret REST API key | Server-side v2 customer subscription and product lookups |
Authentication
POST /webhooks/{workspace_id}/revenuecatAuthorization: Bearer YOUR_WEBHOOK_SECRETContent-Type: application/jsonMissing or mismatched tokens return 401. Unconfigured secret returns 403.
Event Matrix
| Event | Credit action | Subscription state |
|---|---|---|
INITIAL_PURCHASE (subscription) | Grant first period (or first annual monthly tranche). Unfreezes all top-ups. | Record subscription, status → Active |
INITIAL_PURCHASE (consumable) | Grant top-up credits. If no active sub, the grant lands in the frozen bucket. | No change |
RENEWAL | Forfeit previous period grant, create new one. Idempotent unfreeze. | No change |
NON_RENEWING_PURCHASE | Same as consumable initial purchase. | No change |
PRODUCT_CHANGE | Informative only. Never grants or modifies credits. Wait for the companion RENEWAL/INITIAL_PURCHASE. | Record pending change (deferred transitions) |
CANCELLATION (refund) | Void the refunded grant(s) via rc_transaction_id. Balance clamps at zero. | Status → Canceled if non-renewing |
CANCELLATION (voluntary) | No credit change | Status → Canceled |
EXPIRATION | Freezes all top-ups atomically. Subscription credits finish their period and then forfeit. | Clear subscription |
BILLING_ISSUE | No credit change | Status → BillingIssue, record grace period |
UNCANCELLATION | No credit change | Status → Active |
TRANSFER | No credit change | Live-sync each transferred_to customer from RevenueCat v2 |
Propagation to /my/credits
After every handled event Athena invalidates the customer-status cache
(keyed by external customer ID). The next GET /my/credits — and every
authenticated request from that customer — sees the updated bucket balances
(active_credits / frozen_credits / inactive_credits) immediately, without
waiting for cache TTL.
This applies to every event in the matrix above: purchases, renewals, expirations, cancellations, refunds, plan changes, and billing issues all propagate within a single Redis round-trip of the webhook’s DB write.
GET /my/credits is a local Athena read. It does not call RevenueCat. When the
client needs to repair races after Purchases.logIn(...), restore, purchase, or
plan change, call GET /my/billing/refresh. That endpoint performs a live
RevenueCat v2 lookup first, updates Athena’s local entitlement state, and then
returns the same balance shape as /my/credits.
Idempotency
RevenueCat may deliver the same event more than once. Athena deduplicates on
event.id (preferred) or transaction_id. It is safe to receive duplicates.
Deduplication Key
| Field checked | Source |
|---|---|
event.id | RevenueCat event envelope |
transaction_id | RevenueCat transaction object |
Duplicate events return 200 without re-applying credit mutations.
Error Responses
| HTTP | Cause |
|---|---|
400 | Missing app_user_id, missing product_id, unknown product, invalid transfer |
401 | Missing or invalid Authorization: Bearer token |
403 | RevenueCat Webhook Secret not configured on workspace |
503 | Credit service not available |
Media Generation Webhooks (Fal.ai)
Athena registers webhooks with Fal.ai automatically when a MediaGeneration node submits a job. You do not configure these manually — they are internal to the platform.
When Fal.ai delivers the result, Athena:
- Validates the HMAC-SHA256 signature (5-minute timestamp tolerance)
- Injects
ai_result:{node_id}into the execution variables - Resumes the paused execution from checkpoint
Security
Fal.ai webhooks require these headers:
X-Fal-Signature— HMAC-SHA256 of the bodyX-Fal-Timestamp— Unix timestamp (must be within 5 minutes)
Missing headers return 401. Expired timestamps return 401.
Retry Policy
If the execution is not in WaitingForExternalEvent state when the webhook
arrives, Athena returns 400 bad_request. Fal.ai will retry delivery
according to its own retry schedule.
General Webhook Best Practices
For webhooks your own server sends to Athena (RevenueCat integration):
- Sign everything — use the webhook secret, rotate it periodically
- Idempotency keys — include a stable event ID in the request for dedup
- Timeouts — Athena processes RevenueCat webhooks within 10 seconds; do not configure RevenueCat timeout below 15 seconds
For webhooks Athena sends outward (Action nodes in workflows):
- Your endpoint should return
2xxwithin 30 seconds (default timeout) - Maximum timeout is 10 minutes (configurable per Action node)
- Athena includes an
Idempotency-Keyheader when configured on the node - SSRF protection blocks requests to private IP ranges and cloud metadata endpoints
Related Docs
- Credits — three-bucket credit ledger and RevenueCat event handling
- Architecture — workflow engine MediaGeneration node
- Execute — MediaGeneration SSE events in the streaming endpoint
- Error Codes — webhook error codes