Webhooks
STOA Platform can notify external systems when subscription lifecycle events occur. Webhooks deliver HTTP POST requests to your endpoints with signed payloads.
Event Typesβ
| Event | Trigger | Payload Includes |
|---|---|---|
subscription.created | New subscription request | Subscription ID, API, consumer, plan |
subscription.approved | Subscription approved by admin | Subscription ID, API key (if generated) |
subscription.revoked | Subscription revoked | Subscription ID, reason |
subscription.key_rotated | API key rotated | Subscription ID, new key prefix |
subscription.expired | Subscription TTL expired | Subscription ID, expiry date |
Use ["*"] to subscribe to all event types.
Creating a Webhookβ
Via APIβ
The examples below use environment variables. Set them for your STOA instance:
export STOA_API_URL="https://api.gostoa.dev" # Replace with your domain
export STOA_AUTH_URL="https://auth.gostoa.dev" # Keycloak OIDC provider
export STOA_GATEWAY_URL="https://mcp.gostoa.dev" # MCP Gateway endpoint
Self-hosted? Replace gostoa.dev with your domain.
curl -X POST "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-system.example.com/stoa-events",
"events": ["subscription.created", "subscription.approved"],
"secret": "your-hmac-secret-min-32-chars-long",
"enabled": true,
"custom_headers": {
"X-Source": "stoa-platform"
}
}'
Via Portalβ
- Navigate to Webhooks in the Portal sidebar
- Click Create Webhook
- Enter the target URL and select event types
- Set a signing secret (min 32 characters)
- Optionally add custom headers
- Click Save
Payload Formatβ
Every webhook delivery sends a JSON payload:
{
"event": "subscription.approved",
"timestamp": "2026-02-13T10:30:00Z",
"webhook_id": "wh_abc123",
"data": {
"subscription_id": "sub_xyz789",
"api_id": "api_456",
"api_name": "Payment API",
"consumer_id": "consumer_012",
"plan": "standard",
"status": "approved"
}
}
HMAC-SHA256 Signature Verificationβ
Every delivery includes a signature header for payload verification:
X-STOA-Signature: sha256=<hex-encoded-hmac>
Verification Example (Python)β
import hmac
import hashlib
def verify_signature(payload: bytes, secret: str, signature_header: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
received = signature_header.removeprefix("sha256=")
return hmac.compare_digest(expected, received)
Verification Example (Node.js)β
const crypto = require('crypto');
function verifySignature(payload, secret, signatureHeader) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const received = signatureHeader.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
}
Always use timing-safe comparison to prevent timing attacks.
Retry Policyβ
Failed deliveries (non-2xx response or timeout) are retried with exponential backoff:
| Attempt | Delay | Cumulative Wait |
|---|---|---|
| 1 | Immediate | 0 |
| 2 | 1 minute | 1 min |
| 3 | 5 minutes | 6 min |
| 4 | 15 minutes | 21 min |
| 5 | 1 hour | 1h 21min |
After 5 failed attempts, the delivery is marked as failed. No further retries are attempted automatically.
Delivery Trackingβ
View Delivery Historyβ
curl "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries" \
-H "Authorization: Bearer $TOKEN"
Each delivery record includes:
| Field | Description |
|---|---|
status | success, failed, pending |
status_code | HTTP response code from your endpoint |
response_body | First 1KB of response (for debugging) |
attempt | Attempt number (1-5) |
created_at | Delivery timestamp |
Retry a Failed Deliveryβ
curl -X POST "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry" \
-H "Authorization: Bearer $TOKEN"
Testing Webhooksβ
Send a test event to verify your endpoint before going live:
curl -X POST "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks/{webhook_id}/test" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"event_type": "subscription.created"}'
The test delivery uses synthetic data and is marked as test: true in the payload.
Managing Webhooksβ
List Webhooksβ
curl "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks" \
-H "Authorization: Bearer $TOKEN"
Disable a Webhookβ
curl -X PATCH "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks/{webhook_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
Delete a Webhookβ
curl -X DELETE "${STOA_API_URL}/v1/tenants/{tenant_id}/webhooks/{webhook_id}" \
-H "Authorization: Bearer $TOKEN"
Best Practicesβ
- Always verify signatures β reject unsigned or incorrectly signed payloads
- Respond quickly β return 200 within 5 seconds; process asynchronously
- Handle duplicates β use
webhook_id+timestampfor idempotency - Use HTTPS β webhook URLs must use TLS in production
- Rotate secrets β update the webhook secret periodically via PATCH
- Monitor deliveries β check the Portal delivery history for failures
Relatedβ
- Subscriptions Lifecycle -- Subscription events
- Consumer Onboarding -- API key management
- Developer Portal -- Portal webhook UI