Skip to content

Publishing

Add a source, store its publish key, and publish events from an app.

An app publishes events into one source with a publish key.

Add a source

  1. On the albastov.com admin panel, open Events and create a source: slug ([a-z0-9-]{2,48}), name, icon, colour and retention in days (default 90).
  2. Mint a publish key for the source. The panel shows the abk_ secret once, with a brief for the app's developer or coding agent.
  3. Store the key on the app's server as ALBASTOV_EVENTS_KEY. Keep the key out of browser bundles, mobile apps, repositories and log lines.

A source takes events from publish keys, from a webhook, or both.

Name the source

Readers show every event beside its source's name, icon and colour. Set them once from the app with PATCH /v1/events/sources/self; the change applies to the source's whole history.

FieldContent
nameThe service as readers know it: Stripe, GitHub, Shop
iconA Simple Icons slug (stripe) or an SF Symbol name (shippingbox.fill)
colorThe brand colour: #rgb, #rrggbb or #rrggbbaa

Use one source per service a reader recognises. Put the part of the service in each event's subject (api, checkout), not in the source name.

curl

curl -X PATCH https://api.albastov.com/v1/events/sources/self \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sentry", "icon": "sentry", "color": "#362d59"}'
Response
{"slug": "sentry", "name": "Sentry", "icon": "sentry", "color": "#362d59"}

Publish an event

POST /v1/events stores one event in the key's source and returns the stored event. The source comes from the key: leave source out.

curl

curl https://api.albastov.com/v1/events \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  --max-time 5 \
  -d '{"type": "deploy.failed", "severity": "error", "text": "Deploy **failed** on web `9f3c2e1`", "detail": "npm run build exited 1", "display": {"lifetime": "sticky"}, "idempotency_key": "deploy-9f3c2e1"}'

Python

import json, os, urllib.request

req = urllib.request.Request(
    "https://api.albastov.com/v1/events",
    data=json.dumps({
        "type": "deploy.failed",
        "severity": "error",
        "text": "Deploy **failed** on web `9f3c2e1`",
        "detail": "npm run build exited 1",
        "display": {"lifetime": "sticky"},
        "idempotency_key": "deploy-9f3c2e1",
    }).encode(),
    headers={
        "User-Agent": "my-app/1.0",
        "Authorization": f"Bearer {os.environ['ALBASTOV_EVENTS_KEY']}",
        "Content-Type": "application/json",
    },
    method="POST",
)
with urllib.request.urlopen(req, timeout=5) as res:
    print(res.status, json.load(res)["id"])

JavaScript

const res = await fetch("https://api.albastov.com/v1/events", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ALBASTOV_EVENTS_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "deploy.failed",
    severity: "error",
    text: "Deploy **failed** on web `9f3c2e1`",
    detail: "npm run build exited 1",
    display: { lifetime: "sticky" },
    idempotency_key: "deploy-9f3c2e1",
  }),
  signal: AbortSignal.timeout(5000),
});
console.log(res.status, (await res.json()).id);
Response
{"id": "evt_01JBX7Q2W8V5K3M9N4P6R2T8YC", "cursor": "1734", "source": "web", "source_name": "Web", "source_color": null, "source_icon": null, "type": "deploy.failed", "severity": "error", "text": "Deploy **failed** on web `9f3c2e1`", "detail": "npm run build exited 1", "subject": null, "url": null, "occurred_at": "2026-11-03T10:02:03Z", "received_at": "2026-11-03T10:02:03Z", "display": {"lifetime": "sticky"}, "data": null, "dismissed_at": null}

Name the type as a dotted noun (deploy.succeeded, pr.merged, site.down) and keep text to one line a person reads at a glance. Mark states that last until a person acts with "display": {"lifetime": "sticky"}. Severity guidance: Event fields.

Publish a batch

POST /v1/events/batch publishes 1 to 100 events in one request, as {"events": [...]}. One invalid event refuses the whole batch, and the error's meta.index names the event. The response lists the stored events in request order, with status 201 when at least one event is new, else 200.

Shell
curl https://api.albastov.com/v1/events/batch \
  -H "Authorization: Bearer $ALBASTOV_EVENTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"events": [{"type": "job.done", "text": "Job **export** done"}, {"type": "job.done", "text": "Job **import** done"}]}'

Dedupe

Set idempotency_key on every event the app may send twice: deploy-<sha>, backup-2026-11-03. A key used again on the same source within 7 days returns the original event with 200 and stores nothing.

Retries

Publish outside the request path, with a 5 s timeout. A failed publish must not block, slow down or fail the app.

ResponseAction
Network error, 5xxRetry at most 3 times, after 1 s, 4 s and 15 s plus jitter; then drop the event and log one line
429Wait the Retry-After seconds, then retry within the same 3 attempts
400, 401, 403, 413Do not retry; log message

Rates and body sizes: Limits.

Reference

  • Events reference: fields, display values, responses and error codes for POST /v1/events, POST /v1/events/batch and PATCH /v1/events/sources/self.