SeptemCore LogoSeptemCore
API ReferencegRPC API

Integration Hub gRPC Service Reference

IntegrationHubService gRPC reference for platform.integration_hub.v1. 11 RPCs across 3 groups: Provider Registry (6), Outbound Calls (1), Dead Letter Queue (4). Enums: ProviderType, AuthType, ProviderStatus, CircuitBreakerState. Shared messages: Provider, ProviderHealth, DLQEntry, CallResponse. Circuit breaker FSM and credential encryption details.

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.
  int32          page_size  = 4;
  string         page_token = 5;
}

Response — ListProvidersResponse

message ListProvidersResponse {
  repeated Provider data            = 1;
  string            next_page_token = 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.
  int32  page_size   = 3;
  string page_token  = 4;
}

Response — ListDLQResponse

message ListDLQResponse {
  repeated DLQEntry data            = 1;
  string            next_page_token = 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

On this page