Skip to main content

Authentication

Nerve has two authentication mechanisms, because it has two kinds of caller.

Who is callingHow they authenticateWhich routes
Your applicationAuthorization: Bearer <API key>Everything under /v1/ except health and webhooks
Your providersPer-provider HMAC signaturePOST /v1/webhooks/{provider}
Nobody / your load balancerNo authGET /v1/health

Bearer API keys

Send your workspace API key in the Authorization header on every request:

curl https://api.nervly.io/v1/events/trigger \
-H "Authorization: Bearer nerve_sk_live_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ ... }'

A missing, malformed, or unrecognised key returns 401 Unauthorized:

{
"error": "UNAUTHORIZED",
"message": "Missing or malformed Authorization Bearer header",
"status_code": 401
}

Live vs. Test API keys

Nerve issues two modes of API keys:

Key PrefixModeDelivery BehaviourCost
nerve_sk_live_...LiveDispatches to real external providers (SMS, Email, Push, WhatsApp)Real provider cost
nerve_sk_test_...TestShort-circuits provider execution; emits synthetic nerve_test_{event_id}Zero

In Test Mode:

  • Zero outbound network calls are made to external providers.
  • Messages and timelines are still recorded normally in PostgreSQL and can be inspected in the dashboard.
  • Safe for local integration tests, staging environments, and CI pipelines without running up provider bills or sending accidental OTPs to real users.

Security and verification guarantees

  1. Constant-Time Comparison: The gateway resolves keys using constant-time comparison (subtle::ConstantTimeEq HMAC-SHA256) to eliminate timing side-channel attacks.
  2. One-Time Reveal: When created in the dashboard, the raw API key is displayed once in a modal. Only a cryptographic hash (SHA-256) is stored in the database.
  3. Sub-millisecond Key Lookups: Resolved keys are cached briefly to keep high-throughput ingest off the database, keyed by a non-reversible digest of the credential.
  4. Immediate Revocation: Revoking a key in the dashboard invalidates the cache entry at once. In-flight requests using the revoked key fail immediately with 401.

Webhook signatures

Your providers cannot present your API key, so POST /v1/webhooks/{provider} sits outside the bearer-token surface and authenticates each provider on its own terms:

  • Termii: Signs the raw request body with HMAC-SHA256 under your Termii webhook secret and sends the hex digest as X-Termii-Signature.
  • Paystack: Inbound payment webhooks verify the x-paystack-signature HMAC-SHA512 header over the raw body before parsing JSON.
  • Twilio / SendGrid / Postmark: Verified using provider-specific signature headers and token validation.

Health checks

GET /v1/health needs no authentication, by design — a load balancer should not need a secret to decide whether to route traffic. It exposes the running service version, environment, and broker connectivity:

curl https://api.nervly.io/v1/health
{
"status": "OK",
"service": "nerve-gateway",
"version": "0.1.0",
"environment": "production",
"uptime_seconds": 86400,
"nats_connected": true
}

Next