Internal APIs -- Overview & HMAC Authentication

Internal APIs enable service-to-service communication within the Sphere platform. They use HMAC-SHA256 signatures for authentication instead of JWT tokens. These endpoints are not exposed through the Traefik gateway and should never be called from client applications.


HMAC-SHA256 authentication

Every internal API request must include an X-Sphere-Signature header containing a timestamped HMAC-SHA256 signature. The receiving service verifies this signature against its configured shared secret before processing the request.

Signature format

The signature header follows this format:

X-Sphere-Signature: t=<timestamp>,v1=<hmac>
  • Name
    t
    Type
    integer
    Description

    Unix timestamp (seconds since epoch) when the request was signed. Used for replay protection.

  • Name
    v1
    Type
    string
    Description

    HMAC-SHA256 hex digest computed over the signing payload.

Signing payload

The HMAC is computed over a concatenation of four components, joined by dots:

timestamp.method.path.body
  • Name
    timestamp
    Type
    string
    Description

    The same Unix timestamp used in the t= parameter.

  • Name
    method
    Type
    string
    Description

    The HTTP method in uppercase (e.g., POST, GET, PATCH).

  • Name
    path
    Type
    string
    Description

    The request path without query string (e.g., /api/internal/orchestration/provision/tenant).

  • Name
    body
    Type
    string
    Description

    The raw request body as a string. For requests with no body (e.g., GET), use an empty string.


Signing example

Here is how to compute an HMAC signature for an internal API request. Each service stores its shared secret in its .env file. The sending and receiving services must share the same secret.

cURL with HMAC

# Compute the signature
TIMESTAMP=$(date +%s)
METHOD="POST"
PATH="/api/internal/orchestration/provision/tenant"
BODY='{"tenant_id":"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d","tenant_short_id":"acme","name":"Acme Corp"}'
SECRET="your-shared-hmac-secret"

PAYLOAD="${TIMESTAMP}.${METHOD}.${PATH}.${BODY}"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

curl -X POST http://core:7002/api/internal/orchestration/provision/tenant \
  -H "Content-Type: application/json" \
  -H "X-Sphere-Signature: t=${TIMESTAMP},v1=${SIGNATURE}" \
  -d "$BODY"

JavaScript

import crypto from 'crypto'

const timestamp = Math.floor(Date.now() / 1000)
const method = 'POST'
const path = '/api/internal/orchestration/provision/tenant'
const body = JSON.stringify({
  tenant_id: '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d',
  tenant_short_id: 'acme',
  name: 'Acme Corp',
})
const secret = 'your-shared-hmac-secret'

const payload = `${timestamp}.${method}.${path}.${body}`
const signature = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex')

const response = await fetch(
  'http://core:7002/api/internal/orchestration/provision/tenant',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Sphere-Signature': `t=${timestamp},v1=${signature}`,
    },
    body,
  },
)

Replay protection

The receiving service enforces a 5-minute window on the timestamp. If the t= value is more than 5 minutes in the past or in the future relative to the server clock, the request is rejected with a 401 Unauthorized response. This prevents replay attacks where a captured request is re-sent at a later time.


Shared secret configuration

Each service stores the HMAC shared secret in its .env file. The exact variable name depends on the service pair. The reset-world.sh script propagates these secrets automatically during setup.

.env configuration

# In sphere-core .env
SPHERE_INTERNAL_HMAC_SECRET=your-shared-hmac-secret

# In sphere-auth .env
SPHERE_INTERNAL_HMAC_SECRET=your-shared-hmac-secret

Architecture

Internal APIs follow a direct service-to-service communication model:

  • Not exposed through Traefik -- internal endpoints use direct URLs (e.g., http://core:7002) rather than the gateway
  • Same HMAC secret -- all services in a sphere instance share a single HMAC secret
  • No JWT required -- internal requests are authenticated by signature, not by user tokens
  • Docker DNS -- in containerized deployments, services resolve each other by Docker DNS names (e.g., auth:7001, core:7002)

Internal API categories

The following categories of internal APIs are available:

  • Name
    Orchestration
    Type
    /internal/orchestration
    Description

    Tenant and user lifecycle management. Provision and deprovision tenants and users across all engines.

  • Name
    User Sync
    Type
    /internal/user-sync
    Description

    Synchronize user profile data from sphere-auth to sphere-core when profiles are updated.

  • Name
    Engine Registration
    Type
    /internal/engine-registration
    Description

    Register engines with sphere-core, project messages, and receive events from engines.

Was this page helpful?