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 proxy —
CallProviderproxies 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 viaRegisterProviderandUpdateProvider.
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 type | How credentials are injected |
|---|---|
API_KEY | Injected as X-Api-Key: {key} or ?api_key={key} per provider config |
OAUTH2 | Access token fetched from provider's token endpoint; cached in Valkey |
BASIC | Authorization: Basic {base64(user:pass)} |
BEARER | Authorization: Bearer {token} |
HMAC | Request 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
| Parameter | Value |
|---|---|
| Failure threshold | 5 consecutive failures within 30 seconds |
| OPEN duration | 60 seconds (INTEGRATION_CIRCUIT_BREAKER_OPEN_SEC=60) |
| HALF_OPEN probes | 1 test request — success → CLOSED; failure → OPEN again |
| State persistence | Circuit 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"}.
}
| Field | Notes |
|---|---|
credentials | JSON object with provider-specific keys. E.g. for API_KEY: {"key": "sk_live_..."}. Encrypted with AES-256-GCM via services/shared/crypto/. |
config.timeout_sec | Request timeout in seconds. Default: 10. Max: 60. |
config.retry_count | Max retries before DLQ. Default: 3. Max: 10. |
config.rate_limit_rps | Max outgoing requests per second. Default: 100. |
Response — RegisterProviderResponse
message RegisterProviderResponse {
Provider provider = 1; // Credentials field absent — never returned.
}
gRPC errors:
| gRPC status | Condition |
|---|---|
INVALID_ARGUMENT | Missing required field or malformed credentials JSON |
ALREADY_EXISTS | Provider 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.
}
| Field | Notes |
|---|---|
path | Relative path. Final URL = provider.base_url + path. E.g. /v1/charges. |
headers | Do not include auth headers — they are injected automatically from stored credentials. |
idempotency_key | Required 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 status | Condition |
|---|---|
NOT_FOUND | provider_id does not exist for this tenant |
UNAVAILABLE | Circuit breaker OPEN or max retries exhausted (entry written to DLQ) |
RESOURCE_EXHAUSTED | Per-provider rate limit (INTEGRATION_RATE_LIMIT_PER_SEC=100) exceeded |
DEADLINE_EXCEEDED | External 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
| Parameter | Value | Config env variable |
|---|---|---|
| Max retries per call | 3 (configurable per provider via config.retry_count) | — |
| Retry strategy | Exponential backoff with jitter | — |
| Backoff base | 1 second, doubles per attempt | — |
| Max timeout per request | 10 seconds (configurable via config.timeout_sec) | — |
| Outgoing rate limit | 100 req/sec per provider | INTEGRATION_RATE_LIMIT_PER_SEC=100 |
| DLQ trigger | Max retries exhausted | — |
Security — Credential Storage
| Layer | Detail |
|---|---|
| Encryption algorithm | AES-256-GCM |
| Key management | Envelope encryption: DEK → KEK → Master Key (HashiCorp Vault, FIPS 140-3 Level 3) |
| Key rotation | Every 90 days via Vault lease renewal |
| Read access | Credentials decrypted only in memory at call time; never logged or returned via gRPC |
| Access control | integration.provider.manage RBAC permission required to register/update providers |