# Webhooks ## Overview Webhooks allow you to receive HTTP callbacks when events occur in your Synapse mind. When an event triggers, Synapse sends a `POST` request to the URL you configured, with a JSON body containing event data and a HMAC-SHA256 signature for verification. Webhooks have a **20 webhooks per mind** limit. ## Events | Event | Description | |-------|-------------| | `memory.created` | Fired when a new memory is stored | | `memory.updated` | Fired when a memory is updated | | `memory.deleted` | Fired when a memory is deleted | | `memory.verified` | Fired when a memory is verified | | `memory.unverified` | Fired when a memory is unverified | | `task.updated` | Fired when a scheduled task status changes | | `chat.message_sent` | Fired when a chat message is sent | ## Create Webhook `POST /webhooks` Create a new webhook for the authenticated mind. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | yes | The URL to call when the event fires. Must be HTTPS (or `http://localhost` for development). | | `events` | string | yes | Comma-separated event patterns. Use `*` for all events. Examples: `memory.*`, `chat.*` | | `secret` | string | yes | Secret for HMAC-SHA256 signature verification. | ### Response | Field | Type | Description | |-------|------|-------------| | `id` | string (UUID) | Webhook ID | | `url` | string | Webhook URL | | `events` | string | Event pattern (e.g. `memory.*`) | | `mind_id` | string (UUID) | Mind that owns this webhook | | `enabled` | boolean | Whether the webhook is enabled | | `created_at` | number | Unix timestamp (seconds) | ## List Webhooks `GET /webhooks` Lists all webhooks for the authenticated mind. ### Response Array of webhook objects with the following fields: | Field | Type | Description | |-------|------|-------------| | `id` | string (UUID) | Webhook ID | | `url` | string | Webhook URL | | `events` | string | Event pattern (e.g. `memory.*`) | | `mind_id` | string (UUID) | Mind that owns this webhook | | `enabled` | boolean | Whether the webhook is enabled | | `created_at` | number | Unix timestamp (seconds) | | `last_call` | number or null | Unix timestamp of last call | | `last_status` | number or null | HTTP status code of last call | | `last_error` | string or null | Error message from last call | ## Get Webhook `GET /webhooks/:id` Gets a single webhook by ID. Requires ownership. ### Response Same fields as List. ## Update Webhook `PUT /webhooks/:id` Update a webhook's URL, events, or secret. Requires ownership. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | no | New webhook URL | | `events` | string | no | New event patterns | | `secret` | string | no | New secret | ## Enable / Disable Webhook `POST /webhooks/:id/enable` Enable a webhook. Requires ownership. `POST /webhooks/:id/disable` Disable a webhook. Requires ownership. ## Test Webhook `POST /webhooks/:id/test` Sends a test event (`memory.created`) to the webhook URL. Requires ownership. ## Delete Webhook `DELETE /webhooks/:id` Delete a webhook by ID. Requires ownership. ## Example ```bash curl -X POST https://synapse.schaefer.zone/webhooks \ -H "Authorization: Bearer YOUR_MIND_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhook", "events": "memory.*", "secret": "your-webhook-secret" }' ``` ### Response ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://example.com/webhook", "events": "memory.*", "mind_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "enabled": true, "created_at": 1782756632 } ``` ## Signature Verification The webhook payload includes an HMAC-SHA256 signature in the `X-Synapse-Signature` header. **The signature is computed over the raw request body bytes** (not parsed-then-restringified JSON). ```python import hashlib, hmac # IMPORTANT: Use the RAW request body bytes, not parsed JSON payload_body = request.get_data() # Flask; or request.body in Django/FastAPI sig = hmac.new( webhook_secret.encode(), payload_body, hashlib.sha256 ).hexdigest() expected_header = f"sha256={sig}" actual_header = request.headers.get('X-Synapse-Signature', '') assert hmac.compare_digest(expected_header, actual_header) ``` ## Limits & Auto-Disable - **Rate Limit**: 20 webhooks per mind. - **Auto-Disable**: A webhook returning HTTP 410 (Gone) is automatically disabled. This allows remote services to signal "stop sending" without deleting the webhook configuration. ## Deprecated Event Names | Old Name | New Name | |----------|----------| | `memory.store` | `memory.created` | | `memory.update` | `memory.updated` | | `memory.delete` | `memory.deleted` |