Skip to main content

Documentation Index

Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt

Use this file to discover all available pages before exploring further.

Security Best Practices

Protect your Orbit integration with proper API key management, webhook signature verification, and security hardening.

API Key Management

Key Types

Orbit uses three types of API keys:
Key TypePrefixUsagePermissions
Live Secret Keydv_live_sk_Server-side onlyFull API access
Test Secret Keydv_test_sk_Server-side testingSandbox only
Public Keydv_live_pk_Client-side (web SDK)Limited read-only

Key Security Rules

  1. Never expose secret keys in client-side code. Secret keys (sk_) should only be used in server-side environments.
❌ Client-side JavaScript
const orbit = new Devotel({ apiKey: 'dv_live_sk_xxxx' }) // NEVER

✅ Server-side only
// Node.js backend, Python server, etc.
const orbit = new Devotel({ apiKey: process.env.DEVOTEL_API_KEY })
  1. Use environment variables. Never hardcode keys in source code.
# .env (never commit this file)
DEVOTEL_API_KEY=dv_live_sk_your_key_here
  1. Rotate keys regularly. Generate new keys quarterly and revoke old ones.
  2. Use separate keys per environment. Use test keys (dv_test_sk_) for development.
  3. Restrict key permissions. Create keys with minimum required scopes in the dashboard under Settings > API Keys.

Revoking a Compromised Key

If a key is exposed:
  1. Go to Settings > API Keys in the Orbit dashboard
  2. Click Revoke on the compromised key
  3. Generate a new key
  4. Update your application configuration immediately
Revoking a key is immediate and irreversible. All requests using the revoked key will return 401 INVALID_API_KEY.

Webhook Verification

Every webhook from Orbit includes an HMAC-SHA256 signature in the X-Devotel-Signature header. Always verify this signature before processing webhook payloads.

Signature Format

The header is Stripe-style, not a bare hex digest:
X-Devotel-Signature: t=1715357600,v1=4f9c2e6b8a1d3f5e7c9b1a3d5f7e9c1b3a5d7f9e1c3b5a7d9f1e3c5b7a9d1f3e
  • t=<unix> — Unix timestamp (seconds) when Orbit signed the payload. Use this for replay protection.
  • v1=<hex> — HMAC-SHA256 of <t>.<raw_body> keyed by your webhook signing secret. Hex-encoded.
During a secret rotation grace window the header includes two v1=<hex> values (new secret first, previous secret second). Your verifier should accept the request if any v1 matches your computed HMAC.

How Verification Works

  1. Parse X-Devotel-Signature into t and one or more v1 candidates.
  2. Reject the request if t is older than 5 minutes (replay protection).
  3. Compute expected = HMAC_SHA256(secret, "<t>.<raw_body>") as hex.
  4. Compare each v1 candidate against expected using a timing-safe comparison.

Implementation

Node.js

import crypto from 'node:crypto'

function parseSignatureHeader(header) {
  const parts = header.split(',').map((p) => p.trim())
  let t = null
  const v1s = []
  for (const part of parts) {
    const eq = part.indexOf('=')
    if (eq < 0) continue
    const key = part.slice(0, eq)
    const value = part.slice(eq + 1)
    if (key === 't') t = value
    else if (key === 'v1') v1s.push(value)
  }
  return { t, v1s }
}

function verifyWebhookSignature(rawBody, header, secret) {
  const { t, v1s } = parseSignatureHeader(header)
  if (!t || v1s.length === 0) return false

  const ageSec = Math.floor(Date.now() / 1000) - Number(t)
  if (!Number.isFinite(ageSec) || ageSec < 0 || ageSec > 5 * 60) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`, 'utf8')
    .digest('hex')
  const expectedBuf = Buffer.from(expected, 'hex')

  return v1s.some((candidate) => {
    const candidateBuf = Buffer.from(candidate, 'hex')
    return (
      candidateBuf.length === expectedBuf.length &&
      crypto.timingSafeEqual(candidateBuf, expectedBuf)
    )
  })
}

app.post('/webhooks/orbit', (req, res) => {
  const header = req.headers['x-devotel-signature']
  const isValid = verifyWebhookSignature(
    req.rawBody,
    header,
    'whsec_your_secret',
  )

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  // Process the webhook
  const event = JSON.parse(req.rawBody)
  res.status(200).json({ received: true })
})

Python

import hmac
import hashlib
import time


def parse_signature_header(header: str):
    t = None
    v1s = []
    for part in header.split(","):
        part = part.strip()
        if "=" not in part:
            continue
        key, _, value = part.partition("=")
        if key == "t":
            t = value
        elif key == "v1":
            v1s.append(value)
    return t, v1s


def verify_webhook_signature(raw_body: bytes, header: str, secret: str) -> bool:
    t, v1s = parse_signature_header(header)
    if not t or not v1s:
        return False

    age = int(time.time()) - int(t)
    if age < 0 or age > 5 * 60:
        return False

    signed = f"{t}.".encode("utf-8") + raw_body
    expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()

    return any(hmac.compare_digest(candidate, expected) for candidate in v1s)


@app.route("/webhooks/orbit", methods=["POST"])
def handle_webhook():
    header = request.headers.get("X-Devotel-Signature", "")
    if not verify_webhook_signature(request.data, header, "whsec_your_secret"):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.get_json()
    return jsonify({"received": True}), 200

Go

func parseSignatureHeader(header string) (string, []string) {
    var t string
    var v1s []string
    for _, part := range strings.Split(header, ",") {
        part = strings.TrimSpace(part)
        eq := strings.IndexByte(part, '=')
        if eq < 0 {
            continue
        }
        key, value := part[:eq], part[eq+1:]
        switch key {
        case "t":
            t = value
        case "v1":
            v1s = append(v1s, value)
        }
    }
    return t, v1s
}

func verifyWebhookSignature(rawBody []byte, header, secret string) bool {
    t, v1s := parseSignatureHeader(header)
    if t == "" || len(v1s) == 0 {
        return false
    }

    ts, err := strconv.ParseInt(t, 10, 64)
    if err != nil {
        return false
    }
    age := time.Now().Unix() - ts
    if age < 0 || age > 5*60 {
        return false
    }

    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(t + "."))
    mac.Write(rawBody)
    expected := hex.EncodeToString(mac.Sum(nil))

    for _, candidate := range v1s {
        if hmac.Equal([]byte(candidate), []byte(expected)) {
            return true
        }
    }
    return false
}

Webhook Security Checklist

  • Parse X-Devotel-Signature as t=<unix>,v1=<hex>[,v1=<hex>] — never as a bare hex digest
  • Reject signatures whose t (timestamp) is older than 5 minutes (replay protection)
  • Accept the request if any v1 matches (covers secret rotation grace)
  • Use timingSafeEqual (or equivalent) to prevent timing attacks
  • Respond with 200 within 30 seconds to avoid retries
  • Use HTTPS for all webhook endpoints
  • Store webhook secrets securely (environment variables, secret manager)

Transport Security

HTTPS Required

All Orbit API endpoints require HTTPS (https://orbit-api.devotel.io). HTTP requests are rejected.

TLS Requirements

  • Minimum TLS 1.2
  • TLS 1.3 recommended
  • Weak cipher suites are not supported

Webhook Endpoint Requirements

Your webhook endpoints must:
  • Use HTTPS with a valid SSL certificate (not self-signed)
  • Support TLS 1.2 or higher
  • Respond within 30 seconds
  • Return a 2xx status code to acknowledge receipt

IP Allowlisting

If your firewall restricts inbound traffic, allowlist Orbit’s webhook delivery IPs:
34.141.0.0/16    (europe-west1 — primary)
35.195.0.0/16    (europe-west1 — secondary)
IP ranges may change. Subscribe to the Orbit status page for notifications about infrastructure changes.

Data Security

PII Handling

  • Orbit masks phone numbers in logs (+1415555****)
  • API responses include full data; mask PII in your own logs
  • Use metadata fields for internal identifiers instead of PII

Data Retention

Data TypeRetentionNotes
Message content90 daysConfigurable per account
Call recordings30 daysConfigurable; auto-deleted after
Webhook delivery logs30 daysAvailable in dashboard
API access logs1 yearIncludes request IDs

Encryption

  • In transit: TLS 1.2+ for all API and webhook traffic
  • At rest: AES-256 encryption for all stored data
  • Recordings: Encrypted at rest with per-tenant keys

RBAC (Role-Based Access Control)

Orbit supports five roles with increasing permissions:
RoleAPI KeysSend MessagesManage AgentsBillingTeam
viewerRead onlyNoNoNoNo
developerCreate/manageYesYesNoNo
adminFull accessYesYesViewInvite
billingNoNoNoFullNo
ownerFull accessYesYesFullFull
Assign the minimum role needed for each team member.

Security Headers

Orbit API responses include security headers:
HeaderValue
X-Request-IdUnique request identifier for tracing
Strict-Transport-Securitymax-age=31536000; includeSubDomains
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Found a security vulnerability? Report it to security@devotel.io. We take all reports seriously and respond within 24 hours.