Skip to content

Security & Auth

The app system uses a bidirectional authentication model: the platform signs events sent to your runner, and your runner signs actions sent to the platform. The same key material is used in both directions.

Authentication Methods

The platform generates a shared secret during runner registration. Both sides use it for signing.

Inbound (platform -> runner):

  1. Platform computes HMAC-SHA256(secret, requestBody)
  2. Hex signature is sent in X-App-Signature header
  3. Runner verifies by computing the same HMAC and comparing

Outbound (runner -> platform):

  1. Runner computes HMAC-SHA256(secret, requestBody)
  2. Hex signature is sent in X-App-Signature header
  3. Platform verifies the signature

Bearer Token

Developer provides a static token during registration. The platform sends it as Authorization: Bearer {token}.

Custom Header

Developer specifies a header name and value during registration. The platform includes it on every request.

Recommendation

Use hmac-sha256 for the strongest security. It provides integrity verification (the body hasn't been tampered with) and authentication (the request came from the platform).

Permission Scopes

Actions are organized into three tiers based on their impact:

Read Scopes

Read-only access. Never requires approval.

ScopeDescription
crown:readRead token page data (registration, market, social, etc.)
market:readRead market data
apps:readRead app configurations

Write Scopes

Modify data. Some may require approval based on token owner settings.

ScopeDescriptionApproval
crown:metadata:writeUpdate token metadataConfigurable
apps:config:writeUpdate app configConfigurable
apps:storage:writeRead/write app storageNever
notifications:sendSend notificationsNever

Dangerous Scopes

High-impact actions. Always require explicit token owner approval.

ScopeDescription
crown:transfer:initiateInitiate registration ownership transfer
crown:apps:manageEnable/disable apps on a token

Approval Flow

When a runner requests a dangerous action:

  1. Runner sends the action request
  2. Platform creates a pending approval and returns approval_id
  3. Token owner sees the approval in their dashboard
  4. Token owner approves or denies within 24 hours
  5. If approved, the platform executes the action
  6. If denied or expired, the action is discarded

Expiry

Pending approvals expire after 24 hours. The runner must re-request expired actions.

Audit Logging

Every action attempt is logged:

  • Scope: 100% of action API calls (successful and failed)
  • Retention: 90 days
  • Immutable: Logs cannot be modified or deleted

Token owners can view the audit log in their dashboard or via the API:

GET /api/v2/registration/{id}/app-actions

Authentication required: this endpoint is gated by the platform's wallet session auth — send Authorization: Bearer <JWT> for the token owner's or a manager's signed-in session. (An unauthenticated or wrong-wallet request returns 401/403; the action log reveals pending approvals, which is why it is not public.)

Query parameters:

ParameterDescription
appIdFilter by app
actionFilter by action type
statusFilter by status (completed, pending, denied, failed)
sinceISO 8601 start date
limitMax results (default 50)

Rate Limiting

Actions are rate-limited per runner with sliding windows, by category (Read / Write / Storage / Notifications / Dangerous). The limits, the rate-limit response headers, and 429 handling are documented in the Actions API.

Abuse Detection

The platform tracks rate limit violations. After 10 violations within 5 minutes, the runner is automatically suspended. Contact support to re-enable a suspended runner.

Storage Isolation

App storage is strictly isolated:

  • App A cannot read App B's storage
  • Storage keys are scoped to (app_id, crown_id, key) tuples, crown_id is the platform's internal id for a token registration
  • Each app gets its own quota per token — quota amounts and per-request size limits are listed in the Actions API

Request bodies to the Action API are capped at 1 MB.

Best Practices

  1. Always verify signatures before processing webhooks
  2. Use constant-time comparison for signature verification (prevent timing attacks)
  3. Implement idempotency: events may be delivered more than once
  4. Handle unknown events gracefully: log and return ok
  5. Namespace metadata keys with your app ID (e.g., myApp_score)
  6. Store secrets securely: use environment variables or secrets managers, never commit them
  7. Respect rate limits: read Retry-After headers and back off accordingly