Events
An event says that something happened: a deploy finished, a container died, a lead arrived. Every SDK, plugin and hook speaks the same format, defined by the open JSON Schema event.schema.json (protocol version 0).
{ "source": "dokploy", "type": "container.die", "severity": "high", "occurred_at": "2026-11-05T12:00:00Z", "to": ["jimmy"], "payload": { "host": "vps-01", "exit_code": 137 }, "dedup_key": "vps-01/api-v2", "expires_when": "container.healthy", "content": { "summary": "api-v2 died on vps-01", "card": { "title": "Container died", "blocks": [{ "type": "metric", "label": "Crashes in 24h", "value": 3 }] } }}Fields
Section titled “Fields”| Field | Required | Description |
|---|---|---|
source |
Yes | Who generated it (dokploy, crm, finance-agent), 1–120 characters. The SDK fills it from new NotificMe({ source }). |
type |
Yes | Dotted lowercase type: container.die, lead.new. Pattern ^[a-z0-9_-]+(\.[a-z0-9_-]+)*$. |
severity |
Yes | info, low, medium, high or critical. The SDK defaults to info. |
occurred_at |
Yes | When it happened (RFC 3339). The SDK defaults to now. |
id |
No | Your id for the event, used for idempotency. The SDK always sends one. |
to |
No | Recipients: a handle, a member e-mail or a group. Without to, the workspace rules and subscriptions decide. |
payload |
No | Free-form data. The SDK sends it as is (keys are not converted). |
dedup_key |
No | Events with the same key inside the dedup window (1 hour by default, or the event ttl) are merged. |
ttl |
No | How long the notification stays relevant: 30m, 2h, 3d. |
expires_when |
No | An event type that closes this notification, such as container.healthy. |
card_id |
No | Updates an existing card instead of creating another one (a live card). |
retention |
No | ephemeral, standard (default) or regulated. |
content.summary |
With content |
Short text for push, watch and simple channels (up to 240 characters). |
content.card |
No | The declarative card. |
content.rich |
No | A sandboxed HTML reference, always with card as the fallback. |
Recipients and consent
Section titled “Recipients and consent”to only resolves members of the service’s workspace: the current handle, an old handle still reserved, or the e-mail of the membership. Nobody can be notified without consent: a person who has not accepted the invite is not resolved, and POST /v0/events answers 422. Removing a member cuts access immediately. See Workspaces and members.
Idempotency and deduplication
Section titled “Idempotency and deduplication”- Idempotency: the same
idsent twice is stored once. A retry returns the stored response with theIdempotent-Replayed: trueheader. Reusing anidwith different content returns409 idempotency_conflict. - Deduplication: a repeat with the same
dedup_keyin the window is accepted withstatus: "duplicate"andduplicate_of.
Live cards
Section titled “Live cards”Events with the same card_id update the same card instead of creating new notifications. A deploy goes from building to testing to live in one card. With the SDK, pass live: true and then call update():
const deploy = await notific.send({ type: "deploy.progress", summary: "Deploying api", live: true, card: { blocks: [{ type: "progress", value: 0.1, steps: ["Build", "Tests", "Rollout"], current: 0 }] },});
await notific.update(deploy.cardId!, { card: { blocks: [{ type: "progress", value: 0.8, steps: ["Build", "Tests", "Rollout"], current: 2 }] },});Limits
Section titled “Limits”| Limit | Default |
|---|---|
| Events per token | 600 per minute (burst of 100), then 429 rate_limited with Retry-After |
| Body size | 1 MiB |
| Dedup window | 1 hour (the event ttl takes precedence) |
| Idempotency memory | 24 hours |