Orceum is built on the assumption that apps handle sensitive user credentials and data. Here’s what Orceum does, and what you’re responsible for.

What Orceum Handles

ConcernOrceum’s Role
Credential storageEncrypts API keys and OAuth tokens at rest (Fernet symmetric encryption)
Token refreshAutomatically refreshes OAuth tokens before expiry
Token revocationCalls your revoke_url on uninstall
HTTPS enforcementAll Orceum API endpoints are HTTPS-only
Signature verificationVerifies HMAC signatures on events you push
Installation isolationEach user gets a separate installation_id — credentials are never shared

What You’re Responsible For

1. Verify Webhook Signatures

Always verify the X-Orceum-Signature header on lifecycle webhooks. Use constant-time comparison:
import hmac
import hashlib

def verify_signature(body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header.startswith("sha256="):
        return False
    provided = signature_header[len("sha256="):]
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(provided, expected)
Never use == for signature comparison — it’s vulnerable to timing attacks.

2. Sign Events You Push

All events pushed to Orceum must be HMAC-signed. See Webhooks → Signing Requests.

3. Use X-Orceum-Installation-Id for User Isolation

Every action call includes X-Orceum-Installation-Id. Use it to look up the correct user in your database. Never treat all requests as coming from the same user account.
@app.post("/actions")
async def handle_action(
    request: Request,
    x_orceum_installation_id: str = Header(...)
):
    user = await db.get_user_by_installation(x_orceum_installation_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found for this installation")
    # proceed with user-scoped operation

4. Protect Your Secrets

SecretWhere to Store It
orc_* (lifecycle webhook secret)Secrets manager (AWS Secrets Manager, GCP Secret Manager, Vault)
whs_* (event signing secret)Secrets manager
OAuth client_secretSecrets manager / environment variable — never in code
Never commit secrets to source control. Rotate them if accidentally exposed.

5. Implement Least-Privilege OAuth Scopes

Request only the exact scopes your app needs. Users see the requested scopes on the consent screen — over-requesting scopes reduces trust and installs.
// Bad — too broad
"scopes": ["admin", "full_access"]

// Good — minimal and explicit
"scopes": ["tasks:read", "tasks:write", "user:profile:read"]

6. Validate All Inputs

Even though the assistant constructs action parameters from your manifest, always validate inputs server-side. Never trust event_data without validation:
from pydantic import BaseModel, validator
from datetime import date

class CreateTaskInput(BaseModel):
    title: str
    due_date: date | None = None

    @validator("title")
    def title_not_empty(cls, v):
        if not v.strip():
            raise ValueError("Title cannot be empty")
        return v.strip()

7. Use HTTPS Only

Your base_url and webhook URLs must be HTTPS. Orceum will refuse to call HTTP endpoints in production.

8. Handle Idempotently

Orceum may retry requests (429 retries, 401 retry after refresh). Use X-Request-ID as a deduplication key:
@app.post("/actions")
async def handle_action(request: Request, x_request_id: str = Header(...)):
    if await redis.exists(f"req:{x_request_id}"):
        return await redis.get(f"req:{x_request_id}")
    result = await process_action(request)
    await redis.setex(f"req:{x_request_id}", 300, result)
    return result

9. Return Appropriate Status Codes

Return meaningful HTTP status codes — Orceum’s retry/re-auth logic depends on them:
  • 200 — success
  • 400 — bad input (don’t retry)
  • 401 — auth failure (triggers re-auth flow)
  • 429 — rate limited (triggers retry)
  • 500 — server error (don’t retry)

10. Rotate Secrets Periodically

Rotate your OAuth client_secret and Orceum secrets periodically. You can update these values directly in the Orceum Developer Studio immediately after rotation to avoid any gap in service.

Two-Secrets Reference

A common point of confusion for new developers:
SecretPrefixWho Creates ItWho Stores ItUsed For
orc_*orc_Orceum (shown once on creation)YouVerify lifecycle webhooks Orceum sends to your installation_webhook_url. Header: X-Orceum-Signature: sha256=...
whs_*whs_Orceum (visible in dashboard)YouSign events you push to Orceum. Header: X-Hub-Signature-256: sha256=...

Signature Format Differences

DirectionHeaderValue Format
Orceum → Your appX-Orceum-Signaturesha256=<raw_hex>
Your app → OrceumX-Hub-Signature-256sha256=<raw_hex>
In both cases, the HMAC is raw hex with a sha256= prefix in the header value.