Lifecycle webhooks are HTTP POSTs that Orceum sends to your server when a user installs or uninstalls your app. Use them to provision accounts, store installation IDs, clean up data, and revoke credentials. Configure your Installation Webhook URL in the Webhooks section of your app within the Orceum Developer Studio.

INSTALLED Event

Sent immediately after a user completes installation (credentials validated, status → ACTIVE).

Payload

{
  "event": "INSTALLED",
  "app_id": "app_a1b2c3d4",
  "installation_id": "inst_abc123",
  "user_id": "user_xyz789",
  "user_email": "alice@example.com",
  "user_name": "Alice Johnson",
  "installed_at": "2024-01-15T12:00:00Z",
  "metadata": {
    "auth_type": "API_KEY",
    "app_version": "1.0.0"
  }
}

Key Fields to Save

FieldWhy You Need It
installation_idMaps every future action call (X-Orceum-Installation-Id) to the right user
user_idOrceum’s unique identifier for the user
user_emailSeed your own user record or match to existing data
app_idUseful if you handle multiple apps from one webhook URL
Store installation_id → internal_user_id in your database as soon as you receive this event. You’ll use it on every action call to identify the user.

UNINSTALLED Event

Sent when a user removes your app. Use this to clean up resources and revoke any credentials you stored independently.

Payload

{
  "event": "UNINSTALLED",
  "app_id": "app_a1b2c3d4",
  "installation_id": "inst_abc123",
  "user_id": "user_xyz789",
  "uninstalled_at": "2024-01-15T15:30:00Z"
}
On receiving UNINSTALLED:
  • Remove the installation mapping from your database
  • Revoke any tokens or API keys you stored for this user
  • Cancel any active subscriptions or background jobs tied to this installation

Signature Verification

Every lifecycle webhook is signed using your orceum_webhook_secret (the orc_* secret given to you at registration). Always verify signatures before processing.

Signature Headers

POST /webhooks/lifecycle HTTP/1.1
Content-Type: application/json
X-Orceum-Signature: sha256=abc123def456...
X-Timestamp: 2024-01-15T12:00:00Z
The signature is HMAC-SHA256 over the raw request body bytes, using your orc_* secret. The header value includes the sha256= prefix.
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, Header
from typing import Optional

app = FastAPI()

ORCEUM_WEBHOOK_SECRET = "orc_sk_your_secret_here"

def verify_signature(
    body: bytes,
    signature_header: str,
    secret: str
) -> bool:
    """Verify HMAC-SHA256 signature from Orceum lifecycle webhook."""
    if not signature_header.startswith("sha256="):
        return False
    provided_sig = signature_header[len("sha256="):]
    expected_sig = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(provided_sig, expected_sig)

@app.post("/webhooks/lifecycle")
async def lifecycle_webhook(
    request: Request,
    x_orceum_signature: Optional[str] = Header(None)
):
    body = await request.body()

    if not x_orceum_signature or not verify_signature(
        body, x_orceum_signature, ORCEUM_WEBHOOK_SECRET
    ):
        raise HTTPException(status_code=401, detail="Invalid signature")

    payload = await request.json()
    event = payload.get("event")

    if event == "INSTALLED":
        installation_id = payload["installation_id"]
        user_email = payload["user_email"]
        # Store installation_id → user mapping
        await provision_user(installation_id, user_email, payload)

    elif event == "UNINSTALLED":
        installation_id = payload["installation_id"]
        # Clean up
        await deprovision_user(installation_id)

    return {"status": "ok"}
Use hmac.compare_digest (Python) or crypto.timingSafeEqual (Node.js) for signature comparison. Never use == — it is vulnerable to timing attacks.

Signing Key Reference

SecretPrefixWho Generates ItUsed For
orc_*orc_Orceum (shown once on creation)Verifying Orceum’s lifecycle webhooks to you
whs_*whs_Orceum (visible in the dashboard)Verifying events you push to Orceum
The orc_* secret is shown once when you create your app. Store it in your secrets manager immediately.