# Alarum, Integration guide for LLM agents (Claude Code & co.) Alarum is a centralized webhook/event hub. Scripts, CI pipelines, cron jobs and services send "events" to Alarum over a single HTTP POST. The user reads them in a unified inbox, gets push notifications, and can re-forward them to Discord or Slack. This file explains how to integrate Alarum event-sending into a user's project. ## Getting a token Tokens are created by the human user in the Alarum UI (a project's Settings → Tokens, or its Integration tab). A token looks like `alrm_...` and belongs to exactly one project. You do NOT create tokens yourself, there is no management API. Ask the user to create a project and a token, then hand you the token. Treat it as a secret: environment variable or CI secret, never committed. ## Ingestion endpoint ``` POST https://alarum.me/h Authorization: Bearer Content-Type: application/json ``` The token goes in the `Authorization: Bearer ` header (it stays out of access logs and shell history). The request body is a JSON object, the event payload (schema below). Response codes: - `204 No Content`, event accepted - `400 Bad Request`, invalid payload (bad JSON, missing title, invalid level) - `401 Unauthorized`, unknown or revoked token - `429 Too Many Requests`, rate limit hit (1000 events/hour/token); includes a `Retry-After` header ## Payload schema (alarum native format) Only `title` is required. Everything else is optional. | Field | Type | Required | Notes | |-------------|------------|----------|--------------------------------------------------------------| | `level` | enum | no | `info` \| `success` \| `warn` \| `error` \| `critical`, default `info` | | `title` | string | **yes** | max 256 chars | | `message` | string | no | max 8000 chars, multi-line OK | | `tags` | string[] | no | max 16, each max 32 chars, slugified | | `source` | string | no | max 64, e.g. `github-actions`, `cron-backup` | | `url` | string | no | max 1024, link shown in the event detail | | `fields` | object[] | no | max 25, each `{ name, value, inline }`, Discord-embed style | | `timestamp` | string | no | ISO 8601, event time; defaults to received time | | `footer` | string | no | max 256 | | `author` | object | no | `{ name, url }` | | `color` | string | no | `#RRGGBB` hex, overrides the level color | Example body: ```json { "level": "success", "title": "Deploy succeeded", "message": "Build #42 is live on production.", "source": "github-actions", "tags": ["deploy", "prod"], "url": "https://github.com/example/repo/actions", "fields": [ { "name": "Commit", "value": "a1b2c3d", "inline": true }, { "name": "Duration", "value": "3m12s", "inline": true } ] } ``` ## Levels & semantics | Level | Meaning | Push notification | |------------|----------------------------------|---------------------------------------| | `info` | Neutral informational event. | OFF by default | | `success` | Something completed OK. | ON by default | | `warn` | Needs attention but not broken. | ON by default | | `error` | Something failed. | ON by default | | `critical` | Urgent failure. | ON; can bypass the user's quiet hours | Pick the level that matches real severity. Do not send everything as `error`. ## Accepted formats Alarum auto-detects the payload format: - `alarum`, the native schema above. Use this. - `discord`, a Discord webhook payload (`{ "embeds": [...] }`) is accepted and normalized automatically. - `raw`, any other JSON is stored as-is in the `message` field. You can force the format with a header: `X-Alarum-Format: alarum|discord|raw`. ## Examples cURL: ```bash curl -X POST 'https://alarum.me/h' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"level":"success","title":"Deploy succeeded","message":"Build #42 is live","source":"github-actions","tags":["deploy","prod"]}' ``` JavaScript (fetch): ```js await fetch('https://alarum.me/h', { method: 'POST', headers: { 'Authorization': 'Bearer ', 'Content-Type': 'application/json', }, body: JSON.stringify({ level: 'error', title: 'Job failed', message: '...' }), }) ``` Python (requests): ```python import requests requests.post( 'https://alarum.me/h', json={'level': 'warn', 'title': 'Disk almost full', 'message': '92% used'}, headers={'Authorization': 'Bearer '}, ) ``` GitHub Actions (a step in `.github/workflows/*.yml`): ```yaml - name: Notify Alarum run: | curl -X POST 'https://alarum.me/h' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"level":"success","title":"CI passed"}' ``` Codeberg CI / Woodpecker (`.woodpecker.yml`): ```yaml steps: notify-alarum: image: curlimages/curl commands: - | curl -X POST 'https://alarum.me/h' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"level":"success","title":"Pipeline OK"}' ``` GitLab CI (`.gitlab-ci.yml`): ```yaml notify-alarum: image: curlimages/curl:latest script: - | curl -X POST 'https://alarum.me/h' \ -H "Authorization: Bearer $ALARUM_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"level":"success","title":"Pipeline OK","source":"gitlab-ci"}' ``` Node.js (axios): ```js import axios from 'axios' await axios.post( 'https://alarum.me/h', { level: 'error', title: 'Job failed', message: 'See logs' }, { headers: { Authorization: 'Bearer ' } }, ) ``` Go: ```go package main import ( "bytes" "net/http" ) func notify() error { body := bytes.NewBufferString(`{"level":"error","title":"DB connection lost"}`) req, _ := http.NewRequest("POST", "https://alarum.me/h", body) req.Header.Set("Authorization", "Bearer ") req.Header.Set("Content-Type", "application/json") _, err := http.DefaultClient.Do(req) return err } ``` PHP: ```php true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ', 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'level' => 'warn', 'title' => 'Slow query', ]), CURLOPT_RETURNTRANSFER => true, ]); curl_exec($ch); curl_close($ch); ``` Ruby: ```ruby require 'net/http' require 'json' uri = URI('https://alarum.me/h') req = Net::HTTP::Post.new(uri) req['Authorization'] = 'Bearer ' req['Content-Type'] = 'application/json' req.body = { level: 'success', title: 'Deploy ok' }.to_json Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } ``` PowerShell: ```powershell $headers = @{ Authorization = 'Bearer '; 'Content-Type' = 'application/json' } $body = @{ level = 'error'; title = 'Service stopped'; source = $env:COMPUTERNAME } | ConvertTo-Json Invoke-RestMethod -Uri 'https://alarum.me/h' -Method Post -Headers $headers -Body $body ``` Generic bash script (cron-friendly): ```bash #!/usr/bin/env bash # alarum-notify.sh [message] LEVEL=${1:-info}; TITLE=$2; MSG=${3:-} PAYLOAD=$(jq -nc --arg l "$LEVEL" --arg t "$TITLE" --arg m "$MSG" \ '{level:$l,title:$t,message:$m}') curl -fsS -X POST 'https://alarum.me/h' \ -H "Authorization: Bearer ${ALARUM_TOKEN}" \ -H 'Content-Type: application/json' \ -d "$PAYLOAD" ``` ## Best practices - Send events at meaningful checkpoints: deploy done, build failed, backup completed, cron ran, an error was caught. - One token per source, the user can revoke a token without affecting others. - Give a clear, human-readable `title`: it is what shows in the inbox list and in the push notification. - Use `source` to identify where the event came from (CI name, script name). - Use `tags` for filtering (`prod`, `staging`, `deploy`, ...). - Don't spam, the limit is 1000 events/hour/token. Batch or throttle noisy sources. - The token is a secret. Env var or CI secret only, never commit it. ## No management API (yet) There is currently no API to create, edit or delete projects or tokens, everything is managed by the human in the Alarum UI. If you need a project or a token, ask the user to create it and give it to you.