[Proposal][Devportal] Gateway Integration via Outbound Webhooks #1774
Replies: 3 comments
-
|
Related design doc is available at [1]. |
Beta Was this translation helpful? Give feedback.
-
Gateway Webhook Integration GuideWhen a user performs an action (generate a key, subscribe to an API, etc.), the devportal commits the change locally and fires a signed HTTP POST to every registered gateway webhook subscriber. The gateway receives the event and handles whatever enforcement it needs — key storage, rate-limit rule updates, quota enforcement — without any gateway-specific code inside the devportal. How it worksDelivery guarantee: at-least-once per subscriber. Your handler must be idempotent (use User latency: the user gets a response immediately after the devportal commits. Your gateway may take a few seconds (or a backoff retry) to receive the event — plan for a brief propagation window. Registering your gatewayAdd a subscriber entry to webhooks:
subscribers:
- id: my-gateway # stable identifier, used in logs and DB
gatewayType: "my-gateway-type" # must match API.GATEWAY_TYPE in the devportal
url: "https://gateway.example.com/devportal/events"
secret: "a-random-32-char-minimum-secret" # used to sign every request
publicKeyPath: "./cert/publickkey.pem"
events: # allowlist — omit this field to receive all events
- apikey.*
- subscription.*
timeoutMs: 5000 # devportal will treat a non-2xx or timeout as failureGenerating an RSA keypairRun this once on your gateway host: # Generate private key (keep this on the gateway — never share it)
openssl genrsa -out gateway-private.pem 2048
# Derive the public key (paste this into config.yaml on the devportal)
openssl rsa -in gateway-private.pem -pubout -out gateway-public.pem
cat gateway-public.pemVerifying the signatureEvery request includes an Canonical string: Algorithm: HMAC-SHA256 with the shared Verification steps:
Additional headers sent on every request:
Event referenceAll events share a common envelope to ensure consistent parsing across different gateway integrations: {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "apikey.generated",
"occurred_at": "2026-04-24T10:00:00.000Z",
"org_id": "org-uuid",
"gateway_type": "my-gateway-type",
"data": { }
}
|
Beta Was this translation helpful? Give feedback.
-
|
Hi @Piumal1999 When developing the webhook implementation in the platform API side we need the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem statement
The standalone developer portal can host APIs from multiple gateways simultaneously. A single portal may expose APIs running on WSO2 API Platform, Kong, AWS API Gateway, or any custom gateway at the same time. Regardless of where an API is deployed, the consumption experience for devportal users must be consistent: browse APIs, subscribe, generate keys, and manage applications through the same UI and API surface.
For this to work correctly, each gateway must be properly updated internally when a user takes an action. For example, when an api-key is generated, the relevant gateway needs to register it for future validations. When a subscription is created or a plan changes, the gateway needs to apply the corresponding enforcement rules.
The core challenge: the devportal does not know where an API is deployed, what gateway software is running, or how that gateway stores its configuration. It only knows an API's
gateway_type, a string label like"wso2/api-platform"or"wso2/cloud-gateway".Two approaches considered
Option 1: Gateway adapters
Write a plugin/adapter inside the devportal for each supported gateway. When a user generates a key, the devportal calls the adapter, which calls the gateway's admin API.
flowchart LR DP[devportal] --> W[adapter - WSO2] --> WA[WSO2 Admin API] DP --> K[adapter - Kong] --> KA[Kong Admin API] DP --> A[adapter - AWS] --> AA[AWS API GW API]Why we rejected this:
Option 2: Outbound webhooks (chosen)
The devportal fires events. Gateways subscribe and handle them however they need to.
flowchart LR DP["devportal"] -->|signed event| GA["gateway A handler"] DP -->|signed event| GB["gateway B handler"] DP -->|signed event| GC["gateway C handler"]Why this is better:
The key insight: the devportal is the source of truth. Gateways are consumers of that truth. The right pattern for distributing truth to multiple consumers is events, not direct API calls.
Architecture
High-level flow
flowchart TD %% ====================== %% User %% ====================== U["User"] %% ====================== %% Developer Portal %% ====================== subgraph DP["Developer Portal"] API["Devportal REST APIs"] Core["Devportal Core"] DB["Devportal DB (api_keys, subscriptions, DP_EVENT outbox)"] Registry["Subscriber Registry (config)"] Dispatcher["Event Dispatcher (poll pending, match subscribers, create deliveries)"] Worker["Delivery Worker (HMAC sign, HTTP POST, retry, backoff, dead-letter)"] end %% ====================== %% Gateways %% ====================== subgraph G["Gateways"] GA["Gateway A (verify signature, decrypt key, store key)"] GB["Gateway B (verify signature, update quota rules)"] GC["Gateway C (verify signature, enforce plan, store key)"] end %% ====================== %% Relationships with actions %% ====================== U -->|"invoke API (create key / subscribe / update plan)"| API API -->|"call business logic"| Core Core -->|"persist state + emit outbox event"| DB API -->|"return response to user"| U Core -->|"publish pending events"| Dispatcher Dispatcher -->|"read pending events from outbox"| DB Registry -->|"resolve gateway subscriptions by event type"| Dispatcher Dispatcher -->|"create delivery tasks per subscriber"| Worker Worker -->|"send signed webhook event"| GA Worker -->|"send signed webhook event"| GB Worker -->|"send signed webhook event"| GCSequence Diagram
sequenceDiagram autonumber participant U as User participant C as Devportal Core participant D as Event Dispatcher participant W as Delivery Worker participant G as Gateway U->>C: Generate key / Subscribe / Update plan C->>D: Emit domain event C-->>U: 200 OK D->>D: Process event D->>W: Create delivery tasks W->>G: HTTPS webhook (signed event) G-->>W: Response (success / failure) Note over W,G: Retry with backoff on failure G->>G: Apply changes (keys, subscriptions, policies)Data model
DP_API_KEY: Devportal-owned key identity. Stores metadata only; the secret is never written to the database.KEY_IDAPI_IDSUBSCRIPTION_IDORG_IDNAMESTATUSEXPIRES_ATCREATED_BYCREATED_ATREVOKED_ATDP_EVENT: The outbox. One row per domain event.EVENT_IDEVENT_TYPEapikey.generatedORG_IDGATEWAY_TYPEAGGREGATE_TYPEapikey,subscriptionAGGREGATE_IDPAYLOADOCCURRED_ATSTATUSDP_EVENT_DELIVERY: One row per (event x subscriber). Tracks each delivery attempt independently.DELIVERY_IDEVENT_IDSUBSCRIBER_IDTARGET_URLENCRYPTED_FIELDSSTATUSATTEMPT_COUNTNEXT_ATTEMPT_ATLAST_HTTP_STATUSLAST_ERRORLAST_ATTEMPT_ATDELIVERED_ATDesign decisions
Transactional outbox
The event row is written in the same database transaction as the domain change. This eliminates the dual-write problem: if the domain write commits, the event commits with it. If the transaction rolls back, no phantom event is fired. User actions return immediately after the commit — gateway delivery happens asynchronously.
API keys are generated by the devportal, never stored
The devportal generates the key secret, encrypts it immediately per-subscriber using hybrid encryption (AES-256-GCM + RSA-OAEP), and stores only the ciphertext in the delivery row. The plaintext is returned to the user once and never written to any database column. Each gateway decrypts using its own RSA private key, so even a full database compromise does not expose key secrets.
Request signing
Every outbound webhook request is signed with HMAC-SHA256 using a per-subscriber shared secret. A timestamp included in the signature prevents replay attacks. Gateways verify the signature before processing any event.
At-least-once delivery with independent fan-out
Each subscriber gets its own delivery row, retried independently with exponential backoff. A slow or unavailable gateway does not block delivery to other subscribers. Deliveries that consistently fail are dead-lettered and can be retried via admin API.
Subscriber registration via config
Gateway subscribers are declared in
config.yaml. This keeps configuration auditable in version control without requiring a database-backed registry or admin UI. A dynamic registry can be introduced later without changing the event pipeline.Event catalog
subscription.createdsubscription.deletedsubscription.plan_changedapikey.generatedapikey.regeneratedapikey.revokedOut of scope for v1 (can be added without infrastructure changes):
application.*events, OAuth token events.Future work
schema_versionfield to the event envelope for forward compatibility.Beta Was this translation helpful? Give feedback.
All reactions