Webhooks & Callbacks
- Inbound base
https://api.example.com/webhook/**and/api/billing/webhook/**(public URL that providers call).- Spring Security
permitAllby design: third-party servers cannot present a Keycloak token. Each controller authenticates the payload itself. See Authentication.- Outbound
- Tenant-managed subscriptions at
/api/webhooks(HMAC-signed, retried). - Delivery model
- Providers are at-least-once; core de-duplicates on provider ids or content hashes before acting.
Inbound: channel and provider callbacks
All are POST unless a GET is noted. "Credential" is the header or field the handler checks.
| Path | Controller | Credential | Notes |
|---|---|---|---|
/webhook/meta/whatsapp (GET, POST) | MetaWhatsAppWebhookController | POST: X-Hub-Signature-256. GET: hub.mode=subscribe + hub.verify_token (constant-time match with app.meta.webhook-verify-token) | GET echoes hub.challenge as text/plain, else 403. |
/webhook/meta/social (GET, POST) | MetaSocialWebhookController | X-Hub-Signature-256 (POST); same GET handshake | Messenger / Instagram. |
/webhook/twilio/whatsapp | TwilioWebhookController | X-Twilio-Signature validated with the tenant's own auth token | application/x-www-form-urlencoded. |
/webhook/twilio/sms | TwilioSmsWebhookController | X-Twilio-Signature | Form-encoded. |
/webhook/twilio/status | TwilioStatusCallbackController | X-Twilio-Signature | Delivery status callbacks. |
/webhook/twilio/voice, /voice/collect, /voice/menu-collect | TwilioVoiceWebhookController | Twilio signature (same validator) | Voice and DTMF menu flow. |
/webhook/telegram/{botUserId} | TelegramWebhookController | X-Telegram-Bot-Api-Secret-Token, SHA-256 hashed and compared in constant time with the tenant's stored digest; mismatch 403 | Unknown bot is ignored with 200. De-duped on update_id. |
/webhook/slack | SlackWebhookController | X-Slack-Request-Timestamp + X-Slack-Signature | Answers Slack's url_verification handshake (no team_id, no signature check: it echoes a short challenge token and changes nothing); every event_callback resolves the tenant from team_id and must carry a valid signature. |
/webhook/teams/{appId} | TeamsWebhookController | Authorization header (verification scheme not confirmed here) | Bot activity delivery. |
/webhook/discord | DiscordWebhookController | X-Internal-Secret (shared with the Discord gateway service); mismatch 403 | Internal hop from the Node gateway, not from Discord directly. |
/webhook/razorpay | RazorpayWebhookController (core/tenancy) | X-Razorpay-Signature; bad or missing signature 403 via GlobalExceptionHandler | Tenant-state machine for the optional Razorpay gateway (hosted billing; off by default). Dedupe on X-Razorpay-Event-Id. Profile security. |
/api/billing/webhook/{gateway} | BillingWebhookController (core/billing) | X-Razorpay-Signature verified by the resolved gateway client; unknown gateway 404; bad signature 401 | Gateway-agnostic. Always 200 after verification (failures are logged, not re-raised) so the gateway does not retry-storm. |
/webhook/alerts/alertmanager | PlatformAlertWebhookController | Authorization: Bearer <shared-secret> (constant-time compare) | Fails closed: 503 if the secret is unset, 401 on mismatch, else {"recorded": n}. Own filter chain. |
DemoWhatsAppWebhookController and DemoTelegramWebhookController (demo sandbox channels, core/omnichannel/demo),
the OAuth redirect callbacks (/oauth/{google,x,atlassian}/callback) and /scim/v2 are
listed in Authentication but their payload contracts are not documented here.
Inbound processing pattern
sequenceDiagram participant P as Provider participant W as Webhook controller participant T as TenantResolutionService participant D as Dedup (Redis) participant S as AgentChatService / workflow P->>W: POST /webhook/... (signature or secret) W->>T: resolve tenant from provider account id W->>W: verify signature with tenant credential W->>D: claim delivery id D-->>W: new or duplicate W->>S: run agent turn / trigger workflow W-->>P: 200 (fast)
Tenant is resolved from the provider's account identifier (bot id, phone-number id, team id, and so on) through the
control plane, then TenantContext is set to that schema. Deep behaviour per channel is in
Channels & Omnichannel.
Inbound: workflow webhook triggers
| Path | Credential | Behaviour |
|---|---|---|
POST /webhook/workflows/{token} | The secret {token} itself (SHA-256 hashed for lookup); one token per Trigger node | Unknown token or disabled node: 404. Node not in shared-secret auth mode: 409. Workflow not ACTIVE: 409. Redelivery: 409. Success: 200 with the run (WorkflowRunResponse). JSON body is optional; it becomes the run input. |
POST /webhook/workflows/oidc/{workflowId} | Authorization: Bearer <jwt> with a tenant JWT whose realm_access.roles contains workflow-trigger | 503 if no JWT decoder (security profile off). Missing bearer or invalid token: 401. Role or tenant problems: 401/403 per the controller. |
De-duplication on the token path uses the first of X-Delivery-Id, X-Webhook-Id, X-Request-Id that the sender
provides, else a SHA-256 of the body, scoped by trigger node. The workflow-trigger role is a realm role that is not
auto-provisioned; it must be assigned to a service account in Keycloak. Tokens are created and revoked through
GET/POST/DELETE /api/workflows/{workflowId}/nodes/{nodeId}/webhook (PERM_MANAGE_WORKFLOWS_ADMIN). See
Workflow Engine.
Outbound: tenant webhook subscriptions
WebhookSubscriptionController, base /api/webhooks. Writes (POST, PATCH, DELETE) need
PERM_MANAGE_INTEGRATIONS; GETs need any authenticated tenant member.
| Verb and path | Purpose |
|---|---|
GET /api/webhooks | List subscriptions. |
GET /api/webhooks/event-types | Event catalogue (WebhookEventCatalog). |
GET /api/webhooks/{id} | One subscription. |
POST /api/webhooks | Create. Body {name (max 120), url (max 2048), eventTypes[]}. The signing secret is returned in plaintext only here and on rotate. |
PATCH /api/webhooks/{id} | Update name, url, eventTypes, enabled. |
DELETE /api/webhooks/{id} | Delete. |
POST /api/webhooks/{id}/rotate-secret | New signing secret (returned once). |
POST /api/webhooks/{id}/test | Enqueue a synthetic webhook.test delivery; returns {"deliveryId": ...}. |
GET /api/webhooks/{id}/deliveries | Newest 100 deliveries: id, eventType, runId, status, attemptCount, responseCode, lastError, createdAt, deliveredAt, durationMs. |
Event types (catalogue)
workflow.run.started|completed|failed|cancelled|resumed, workflow.node.completed, workflow.output.emitted,
approval.requested, approval.resolved, approval.execution.recorded,
document.generation.completed, an agent-reply-completed event (payload includes agentId, sessionId),
inbound-channel-message events (Discord, X, Telegram, email and Teams entries were read; earlier entries in the catalogue were not), and webhook.test.
Use GET /api/webhooks/event-types for the authoritative list on a deployment.
Delivery contract
- HTTP
POST,Content-Type: application/json,User-Agent: VegaDuta-Webhooks/1.0; connect timeout 5 s, request timeout 15 s; redirects are never followed (a 3xx is a failure). - Headers:
X-VegaDuta-Event,X-VegaDuta-Delivery(delivery id),X-VegaDuta-Signature: t=<unix>,v1=<hex>wherev1 = HMAC-SHA256(secret, "<t>.<raw body>"). - Any 2xx marks the delivery DELIVERED; anything else retries with exponential backoff (30 s doubling, capped at 30 min) up to 10 attempts, then DEAD. At-least-once: receivers must be idempotent (use
X-VegaDuta-Delivery). - The target URL passes the same SSRF guard as remote MCP URLs at save time and again at send time.
- The JSON body envelope is not specified on this page (payload shape per event is not documented in the repository beyond the catalogue descriptions).
# verify (pseudo): reject if abs(now - t) is large, then compare
expected = hex(hmac_sha256(secret, t + "." + raw_body))
constant_time_equal(expected, v1)
Evidence & references
core/security/.../SecurityConfig.java—/webhook/**,/api/billing/webhook/**permitAll,alertWebhookFilterChain, webhook admin gates.core/omnichannel/src/main/java/com/agenticai/core/omnichannel/{meta,twilio,telegram,slack,teams,discord}/*WebhookController.javaandTwilioStatusCallbackController.java.core/workflow/.../WorkflowWebhookController.java,WorkflowController.java(node webhook management).core/billing/.../gateway/BillingWebhookController.java;core/tenancy/.../RazorpayWebhookController.java;core/platform/.../PlatformAlertWebhookController.java.core/eventbus/.../webhook/WebhookSubscriptionController.java,WebhookDeliveryRelay.java,WebhookSignature.java,WebhookEventCatalog.java.- Related: Channels & Omnichannel, Channels & Integrations.