[Proposal][Devportal] Gateway Integration via Outbound Webhooks #1774
Piumal1999
started this conversation in
Ideas
Replies: 1 comment
-
|
Related design doc is available at [1]. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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