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 Type | Prefix | Usage | Permissions |
|---|
| Live Secret Key | dv_live_sk_ | Server-side only | Full API access |
| Test Secret Key | dv_test_sk_ | Server-side testing | Sandbox only |
| Public Key | dv_live_pk_ | Client-side (web SDK) | Limited read-only |
Key Security Rules
- 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 })
- Use environment variables. Never hardcode keys in source code.
# .env (never commit this file)
DEVOTEL_API_KEY=dv_live_sk_your_key_here
-
Rotate keys regularly. Generate new keys quarterly and revoke old ones.
-
Use separate keys per environment. Use test keys (
dv_test_sk_) for development.
-
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:
- Go to Settings > API Keys in the Orbit dashboard
- Click Revoke on the compromised key
- Generate a new key
- 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.
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
- Parse
X-Devotel-Signature into t and one or more v1 candidates.
- Reject the request if
t is older than 5 minutes (replay protection).
- Compute
expected = HMAC_SHA256(secret, "<t>.<raw_body>") as hex.
- 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
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
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 Type | Retention | Notes |
|---|
| Message content | 90 days | Configurable per account |
| Call recordings | 30 days | Configurable; auto-deleted after |
| Webhook delivery logs | 30 days | Available in dashboard |
| API access logs | 1 year | Includes 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:
| Role | API Keys | Send Messages | Manage Agents | Billing | Team |
|---|
viewer | Read only | No | No | No | No |
developer | Create/manage | Yes | Yes | No | No |
admin | Full access | Yes | Yes | View | Invite |
billing | No | No | No | Full | No |
owner | Full access | Yes | Yes | Full | Full |
Assign the minimum role needed for each team member.
Orbit API responses include security headers:
| Header | Value |
|---|
X-Request-Id | Unique request identifier for tracing |
Strict-Transport-Security | max-age=31536000; includeSubDomains |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Found a security vulnerability? Report it to security@devotel.io. We take all reports seriously and respond within 24 hours.