Controlled access · API v1

PsyMind API v1 · Webhooks

Receive the lifecycle, verify it, act once.

Signed, minimal notifications for the nine canonical production events. Use webhooks for timely reactions; use the API Reference to fetch full resources.

01 · Start

Getting started

Organization owners and admins can configure endpoints in the protected Webhook Management Portal. The create response reveals the signing secret once; store it immediately. Service integrations can use the API directly.

curl -X POST "https://psymind.app/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer $PSYMIND_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/psymind/webhooks","events":["practice.ready","outcome.submitted"],"active":true}'

Required service scope: webhooks:write. To send a signed test through the same pipeline:

curl -X POST "https://psymind.app/api/v1/webhook-endpoints/$ENDPOINT_ID/test" \
  -H "Authorization: Bearer $PSYMIND_API_TOKEN"

List endpoint configuration with GET /webhook-endpoints (webhooks:read) and delivery history with GET /webhook-deliveries. See the generated create, test, and history operations.

02 · Contract

Canonical event catalog

These are the nine production event types in api/_webhook-event-registry.js. webhook.test is an internal test delivery type and is not a production domain event.

EventObjectStatusFetch
practice.createdpracticecreatedGET /practices/{id}
practice.readypracticereadyGET /practices/{id}
practice.failedpracticefailedGET /practices/{id}
audio.readyaudioreadyGET /practices/{id}/audio
audio.failedaudiofailedGET /practices/{id}/audio
practice.assignedassignmentassignedGET /assignments/{id}
practice.startedassignmentstartedGET /assignments/{id}
practice.completedassignmentcompletedGET /assignments/{id}
outcome.submittedoutcomesubmittedGET /assignments/{id}/outcomes

03 · Payload

Minimal event envelope

{
  "id": "evt_01J9EXAMPLE123456",
  "type": "practice.ready",
  "created_at": "2026-08-28T10:15:00.000Z",
  "api_version": "2026-08-01",
  "data": { "object": { "id": "prc_01J9EXAMPLE", "object": "practice", "status": "ready" } }
}

The payload contains identifiers and lifecycle status only. Fetch authorized resource details after verifying the signature.

04 · Verify

Verify before parsing

Headers are psymind-event-id, psymind-timestamp, and psymind-signature. Compute HMAC-SHA256 over the exact bytes {timestamp}.{rawBody} using the full whsec_ secret. Accept only a v1= lowercase-hex candidate, compare in constant time, reject timestamps older/newer than 300 seconds, then require the JSON body's id to equal the event-id header.

import { verifyWebhook } from "@psymind/sdk";
const verified = verifyWebhook(rawBody, request.headers, process.env.PSYMIND_WEBHOOK_SECRET);
// Insert verified.id with a UNIQUE constraint before side effects.
from psymind.webhooks import verify_webhook
verified = verify_webhook(raw_body, headers, os.environ["PSYMIND_WEBHOOK_SECRET"])
# Insert verified.id atomically; duplicates return 2xx without repeating work.

Both examples use the published server-side SDK verifiers. Keep the raw request body unchanged until verification.

05 · Reliability

Delivery and retries

  • Any 2xx response succeeds. Redirects, non-2xx responses, network errors, and the 10-second timeout fail.
  • Seven attempts are scheduled at 0, 60, 300, 1,800, 7,200, 43,200, and 86,400 seconds after failure.
  • Retries and manual replay reuse the same event id and payload. Manual retry is available only after terminal failure.
  • Use GET /webhook-deliveries to inspect pending, processing, succeeded, or failed records; POST /webhook-deliveries/{id}/retry queues a terminal failure.
  • Event and successful-delivery history is retained 30 days; failed-delivery metadata 90 days.

Do not assume ordering. Make consumers idempotent and fetch current state when event ordering matters.

06 · Security

Endpoint and secret hygiene

  • Endpoints must be public HTTPS on port 443; private, loopback, link-local, reserved, mixed-DNS, and rebinding destinations are rejected.
  • Secrets are encrypted server-side and returned only on creation or explicit rotation. Rotation returns plaintext once; deletion revokes the endpoint.
  • Payloads are capped at 64 KiB and response previews are sanitized. Never log the signing secret or raw sensitive content.

07 · Diagnose

Common failures

SymptomWhat to check
webhook_signature_mismatchRaw bytes, exact timestamp delimiter, full secret, lowercase hex, and constant-time comparison.
webhook_timestamp_skewClock synchronization and the ±300-second replay window.
webhook_endpoint_timeoutReturn a 2xx within 10 seconds; process asynchronously.
webhook_url_private_addressPublic DNS and HTTPS port 443 without redirects.
webhook_secret_rotation_conflictRetry the complete rotation PATCH; no plaintext is returned on the stale request.

For operation parameters, scopes, status codes, and schemas, use the API Reference. The documentation overview links back here.