Skip to main content

Integration Hub gRPC Service Reference

Package platform.integration_hub.v1 contains the IntegrationHubService — the central proxy for all outbound external API calls from the platform.

Every call to an external service (payment processor, SMS gateway, analytics endpoint, etc.) goes through this service. It provides:

  • Provider Registry — register, retrieve, list, update, and delete external API provider configurations.
  • Outbound call proxyCallProvider proxies requests through a per-provider circuit breaker + retry pipeline.
  • Dead Letter Queue — failed calls after exhausting retries are persisted to the DLQ for inspection and replay.

Security: Provider credentials are stored AES-256-GCM encrypted in PostgreSQL (services/shared/crypto/, envelope encryption via HashiCorp Vault DEK). Credentials are never returned in any response — they are write-only via RegisterProvider and UpdateProvider.

See the gRPC Overview for buf configuration, Go package conventions, deadlines, mTLS, and error mapping.

Proto files:

  • proto/platform/integration_hub/v1/integration_hub_service.proto — service (11 RPCs)
  • proto/platform/integration_hub/v1/integration_hub_messages.proto — messages and enums

Go package: kernel.internal/platform-kernel/gen/go/platform/integration_hub/v1;integrationhubv1


IntegrationHubService — All RPCs

service IntegrationHubService {
// Provider Registry
rpc RegisterProvider(RegisterProviderRequest) returns (RegisterProviderResponse);
rpc RetrieveProvider(RetrieveProviderRequest) returns (RetrieveProviderResponse);
rpc ListProviders(ListProvidersRequest) returns (ListProvidersResponse);
rpc UpdateProvider(UpdateProviderRequest) returns (UpdateProviderResponse);
rpc DeleteProvider(DeleteProviderRequest) returns (DeleteProviderResponse);
rpc RetrieveProviderHealth(RetrieveProviderHealthRequest) returns (RetrieveProviderHealthResponse);

// Outbound Calls
rpc CallProvider(CallProviderRequest) returns (CallProviderResponse);

// Dead Letter Queue
rpc ListDLQ(ListDLQRequest) returns (ListDLQResponse);
rpc RetryDLQEntry(RetryDLQEntryRequest) returns (RetryDLQEntryResponse);
rpc RetryAllDLQ(RetryAllDLQRequest) returns (RetryAllDLQResponse);
rpc DeleteDLQEntry(DeleteDLQEntryRequest) returns (DeleteDLQEntryResponse);
}

Enums

ProviderType

enum ProviderType {
PROVIDER_TYPE_UNSPECIFIED = 0;
PROVIDER_TYPE_PAYMENT = 1;
PROVIDER_TYPE_SMS = 2;
PROVIDER_TYPE_EMAIL = 3;
PROVIDER_TYPE_ANALYTICS = 4;
PROVIDER_TYPE_ADVERTISING = 5;
PROVIDER_TYPE_CUSTOM = 6;
}

AuthType

enum AuthType {
AUTH_TYPE_UNSPECIFIED = 0;
AUTH_TYPE_API_KEY = 1;
AUTH_TYPE_OAUTH2 = 2;
AUTH_TYPE_BASIC = 3;
AUTH_TYPE_BEARER = 4;
AUTH_TYPE_HMAC = 5;
}
Auth typeHow credentials are injected
API_KEYInjected as X-Api-Key: {key} or ?api_key={key} per provider config
OAUTH2Access token fetched from provider's token endpoint; cached in Valkey
BASICAuthorization: Basic {base64(user:pass)}
BEARERAuthorization: Bearer {token}
HMACRequest body HMAC signed with secret; header name per provider config

ProviderStatus

enum ProviderStatus {
PROVIDER_STATUS_UNSPECIFIED = 0;
PROVIDER_STATUS_ACTIVE = 1;
PROVIDER_STATUS_DEGRADED = 2; // Slow or partial failures
PROVIDER_STATUS_DOWN = 3; // Consistently failing
PROVIDER_STATUS_DISABLED = 4; // Administratively disabled
}

CircuitBreakerState

enum CircuitBreakerState {
CIRCUIT_BREAKER_STATE_UNSPECIFIED = 0;
CIRCUIT_BREAKER_STATE_CLOSED = 1; // Normal: requests pass through
CIRCUIT_BREAKER_STATE_HALF_OPEN = 2; // Probe: 1 test request allowed
CIRCUIT_BREAKER_STATE_OPEN = 3; // Tripped: requests fail fast (503)
}

Circuit breaker FSM (sony/gobreaker):

CLOSED ──5 consecutive failures in 30s──► OPEN

wait 60s

HALF_OPEN

┌─── 1 probe request ───┐
│ │
success failure
│ │
CLOSED OPEN
ParameterValue
Failure threshold5 consecutive failures within 30 seconds
OPEN duration60 seconds (INTEGRATION_CIRCUIT_BREAKER_OPEN_SEC=60)
HALF_OPEN probes1 test request — success → CLOSED; failure → OPEN again
State persistenceCircuit state replicated to Valkey for cross-instance consistency

Shared Messages

Provider

message Provider {
string id = 1;
string tenant_id = 2;
string name = 3;
ProviderType type = 4;
string base_url = 5;
AuthType auth_type = 6;
ProviderStatus status = 7;
CircuitBreakerState circuit_breaker_state = 8;
map<string, string> config = 9; // timeout_sec, retry_count, etc.
string created_at = 10; // ISO 8601 UTC
string updated_at = 11; // ISO 8601 UTC
// NOTE: credentials are NEVER returned in responses.
}

Config map: Provider-specific non-secret configuration. Common keys: timeout_sec (request timeout), retry_count (max retries before DLQ), rate_limit_rps (per-provider outgoing rate limit, max 100 req/sec).

ProviderHealth

message ProviderHealth {
string provider_id = 1;
ProviderStatus status = 2;
CircuitBreakerState circuit_breaker_state = 3;
int64 last_success_at_ms = 4; // Unix milliseconds; 0 if never
int64 last_failure_at_ms = 5; // Unix milliseconds; 0 if never
int32 consecutive_failures = 6;
}

DLQEntry

message DLQEntry {
string id = 1;
string provider_id = 2;
string tenant_id = 3;
string method = 4; // HTTP method of original request
string url = 5; // Full URL of original request
map<string, string> headers = 6;
bytes body = 7;
string error = 8; // Last error message
int32 attempts = 9;
string last_retry_at = 10; // ISO 8601 UTC; empty if never retried
string created_at = 11; // ISO 8601 UTC
}

CallResponse

message CallResponse {
int32 status_code = 1; // HTTP status code from external provider
map<string, string> headers = 2;
bytes body = 3;
int64 latency_ms = 4;
}

Provider Registry

RegisterProvider

Registers a new external API provider for a tenant. Credentials are AES-256-GCM encrypted before storage — they are never returned in responses.

Request — RegisterProviderRequest

message RegisterProviderRequest {
string tenant_id = 1; // Required.
string name = 2; // Required. Human-readable name (e.g. "Stripe Production").
ProviderType type = 3; // Required.
string base_url = 4; // Required. e.g. "https://api.stripe.com".
AuthType auth_type = 5; // Required.
bytes credentials = 6; // Required. Raw JSON credentials — encrypted at rest.
map<string, string> config = 7; // Optional. e.g. {"timeout_sec": "10", "retry_count": "3"}.
}
FieldNotes
credentialsJSON object with provider-specific keys. E.g. for API_KEY: {"key": "sk_live_..."}. Encrypted with AES-256-GCM via services/shared/crypto/.
config.timeout_secRequest timeout in seconds. Default: 10. Max: 60.
config.retry_countMax retries before DLQ. Default: 3. Max: 10.
config.rate_limit_rpsMax outgoing requests per second. Default: 100.

Response — RegisterProviderResponse

message RegisterProviderResponse {
Provider provider = 1; // Credentials field absent — never returned.
}

gRPC errors:

gRPC statusCondition
INVALID_ARGUMENTMissing required field or malformed credentials JSON
ALREADY_EXISTSProvider with the same name already registered for this tenant

RetrieveProvider

Request — RetrieveProviderRequest

message RetrieveProviderRequest {
string id = 1; // Required. Provider UUID.
string tenant_id = 2; // Required. Authorization scope validation.
}

Response — RetrieveProviderResponse

message RetrieveProviderResponse {
Provider provider = 1;
}

ListProviders

Request — ListProvidersRequest

message ListProvidersRequest {
string tenant_id = 1; // Required.
ProviderType type = 2; // 0 = all types.
ProviderStatus status = 3; // 0 = all statuses.
platform.common.v1.PaginationRequest pagination = 4;
}

Response — ListProvidersResponse

message ListProvidersResponse {
repeated Provider data = 1;
platform.common.v1.PaginationMeta meta = 2;
}

UpdateProvider

Partial update. optional fields: only set fields are changed. Credentials are replaced only when credentials bytes are non-empty.

Request — UpdateProviderRequest

message UpdateProviderRequest {
string id = 1; // Required. Provider UUID.
string tenant_id = 2; // Required.
optional string name = 3;
optional string base_url = 4;
bytes credentials = 5; // Empty = no credential change.
map<string, string> config = 6; // Empty map = no config change.
optional ProviderStatus status = 7;
}

Response — UpdateProviderResponse

message UpdateProviderResponse {
Provider provider = 1;
}

DeleteProvider

Soft-deletes a provider. Existing DLQ entries for this provider are retained for audit. New CallProvider requests to this provider return NOT_FOUND.

Request — DeleteProviderRequest

message DeleteProviderRequest {
string id = 1; // Required.
string tenant_id = 2; // Required.
}

Response — DeleteProviderResponse

message DeleteProviderResponse {}

RetrieveProviderHealth

Returns live health details including circuit breaker state, consecutive failures, and last success/failure timestamps.

Request — RetrieveProviderHealthRequest

message RetrieveProviderHealthRequest {
string id = 1; // Required.
string tenant_id = 2; // Required.
}

Response — RetrieveProviderHealthResponse

message RetrieveProviderHealthResponse {
ProviderHealth health = 1;
}

Outbound Calls

CallProvider

Proxies an HTTP request to the configured external provider through the circuit breaker + retry pipeline.

Request — CallProviderRequest

message CallProviderRequest {
string provider_id = 1; // Required. Provider UUID.
string tenant_id = 2; // Required.
string method = 3; // Required. "GET"|"POST"|"PATCH"|"DELETE".
string path = 4; // Required. Appended to provider.base_url.
map<string, string> headers = 5; // Additional headers. Auth is auto-injected.
bytes body = 6; // Request body bytes.
string idempotency_key = 7; // UUID v7. Required for non-idempotent methods.
}
FieldNotes
pathRelative path. Final URL = provider.base_url + path. E.g. /v1/charges.
headersDo not include auth headers — they are injected automatically from stored credentials.
idempotency_keyRequired for POST/PATCH/DELETE. UUID v7. Used for DLQ dedup on retry.

Call pipeline:

CallProvider RPC


Rate limit check (INTEGRATION_RATE_LIMIT_PER_SEC=100)


Circuit Breaker state?
├─ OPEN → return UNAVAILABLE immediately (fail-fast, no network call)
└─ CLOSED / HALF_OPEN → proceed


Inject credentials from encrypted store


HTTP request to external provider

├─ Success (2xx) → return CallProviderResponse
└─ Failure → retry (up to config.retry_count, exponential backoff)

└─ After max retries → write DLQEntry → return UNAVAILABLE

Response — CallProviderResponse

message CallProviderResponse {
CallResponse response = 1; // Contains status_code, headers, body, latency_ms
}

gRPC errors:

gRPC statusCondition
NOT_FOUNDprovider_id does not exist for this tenant
UNAVAILABLECircuit breaker OPEN or max retries exhausted (entry written to DLQ)
RESOURCE_EXHAUSTEDPer-provider rate limit (INTEGRATION_RATE_LIMIT_PER_SEC=100) exceeded
DEADLINE_EXCEEDEDExternal provider did not respond within config.timeout_sec

Dead Letter Queue

ListDLQ

Request — ListDLQRequest

message ListDLQRequest {
string tenant_id = 1; // Required.
string provider_id = 2; // Optional: filter by provider.
platform.common.v1.PaginationRequest pagination = 3;
}

Response — ListDLQResponse

message ListDLQResponse {
repeated DLQEntry data = 1;
platform.common.v1.PaginationMeta meta = 2;
}

RetryDLQEntry

Replays a single DLQ entry through the full CallProvider pipeline. Idempotency is enforced via idempotency_key in the original DLQEntry.

Request — RetryDLQEntryRequest

message RetryDLQEntryRequest {
string id = 1; // Required. DLQEntry UUID.
string tenant_id = 2; // Required.
}

Response — RetryDLQEntryResponse

message RetryDLQEntryResponse {
DLQEntry entry = 1; // Updated entry with incremented attempts count.
bool success = 2; // true if retry succeeded; false if still failing.
}

RetryAllDLQ

Replays all pending DLQ entries for a tenant (optionally filtered by provider). Throttled on the service side to avoid overwhelming external APIs.

Request — RetryAllDLQRequest

message RetryAllDLQRequest {
string tenant_id = 1; // Required.
string provider_id = 2; // Optional: retry only entries for this provider.
}

Response — RetryAllDLQResponse

message RetryAllDLQResponse {
int32 total = 1; // Total DLQ entries processed.
int32 succeeded = 2; // Entries successfully replayed.
int32 failed = 3; // Entries still failing after retry.
}

DeleteDLQEntry

Permanently removes a DLQ entry (hard delete). Use when an entry is unrecoverable (e.g., external API no longer accepts the payload).

Request — DeleteDLQEntryRequest

message DeleteDLQEntryRequest {
string id = 1; // Required. DLQEntry UUID.
string tenant_id = 2; // Required.
}

Response — DeleteDLQEntryResponse

message DeleteDLQEntryResponse {}

Retry Policy

ParameterValueConfig env variable
Max retries per call3 (configurable per provider via config.retry_count)
Retry strategyExponential backoff with jitter
Backoff base1 second, doubles per attempt
Max timeout per request10 seconds (configurable via config.timeout_sec)
Outgoing rate limit100 req/sec per providerINTEGRATION_RATE_LIMIT_PER_SEC=100
DLQ triggerMax retries exhausted

Security — Credential Storage

LayerDetail
Encryption algorithmAES-256-GCM
Key managementEnvelope encryption: DEK → KEK → Master Key (HashiCorp Vault, FIPS 140-3 Level 3)
Key rotationEvery 90 days via Vault lease renewal
Read accessCredentials decrypted only in memory at call time; never logged or returned via gRPC
Access controlintegration.provider.manage RBAC permission required to register/update providers