Action Delivery
When a user taps an action button on a notification, TipOff delivers an action event to your integration. Four delivery methods are available:
| Method | Requires agent? | Best for | Example |
|---|---|---|---|
| gRPC Agent | Yes | Always-on local delivery with YAML adapter routing | Donetick, Vikunja task completion |
| SSE | No | Workflow engines, EventSource clients | n8n SSE trigger node |
| Webhook | No | Direct HTTP callbacks (HMAC-signed) | Custom endpoints, serverless functions |
| Polling | No | Simple cron-based integrations | Shell scripts, cron jobs |
Action event payload
Section titled “Action event payload”All delivery methods receive the same JSON payload:
{ "event_id": "evt_xxxxxxxxxxxx", "notification_id": "task-123", "action": "done", "user": "user_abc", "metadata": { "task_id": "123", "source": "vikunja" }, "timestamp": "2026-04-21T12:05:00Z", "source_id": "src_xxxxxxxxxxxx"}gRPC Agent
Section titled “gRPC Agent”The agent binary maintains a persistent bidirectional gRPC stream to the server. Action events are pushed in real time and forwarded as HTTP POSTs to a local endpoint.
TIPOFF_AGENT_API_KEY=sk_your_source_key \TIPOFF_AGENT_GRPC_URL=grpc.tipoff.dev:443 \TIPOFF_AGENT_ADAPTERS_DIR=./adapters \./tipoff-agentThe agent reconnects with exponential backoff (up to 60s) on disconnection. With YAML adapters, the agent can map action events directly to upstream API calls (e.g. “Done” → POST /api/v1/chores/{id}/do on Donetick) without any intermediate webhook processing.
See the Agent Adapters guide for the full YAML adapter reference.
Connect to GET /v1/actions/stream with your source key to receive action events in real time via Server-Sent Events.
curl -N https://api.tipoff.dev/v1/actions/stream \ -H "Authorization: Bearer sk_your_source_key"Events arrive as:
id: 1713700000000-0event: actiondata: {"event_id":"evt_xxx","notification_id":"task-123","action":"done","user":"user_abc","metadata":{},"timestamp":"2026-04-21T12:05:00Z","source_id":"src_xxx"}The id field is a stream-native cursor. On reconnect, send it back via Last-Event-ID to resume from where you left off — no events are lost between connections.
Multiple SSE connections to the same source are fully independent. Each receives all events.
Ideal for n8n’s SSE trigger node or any HTTP client that supports EventSource. A TipOff community node for n8n (included in the server repo under n8n/) wraps the SSE stream as a trigger and provides action/notification nodes for the REST API.
Webhook
Section titled “Webhook”Configure a source with delivery_method: "webhook" and a webhook_url. TipOff POSTs action events directly to your endpoint, signed with HMAC-SHA256.
The signature is in the X-Tipoff-Signature header, computed over the raw request body using the source’s webhook_secret:
# Verify the signatureecho -n "$REQUEST_BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hexExample — verifying in a shell script:
EXPECTED=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "whsec_your_secret" -hex | awk '{print $2}')if [ "$X_TIPOFF_SIGNATURE" = "$EXPECTED" ]; then echo "Valid signature"fiThe webhook_secret is a per-source field set when creating the source. It is never sent over the wire — only used to compute signatures.
Polling
Section titled “Polling”For simple cron-based integrations, poll GET /v1/actions/pending with a cursor and acknowledge events via POST /v1/actions/ack.
# 1. Poll for pending eventscurl -s https://api.tipoff.dev/v1/actions/pending \ -H "Authorization: Bearer sk_your_source_key" | jq .
# 2. Process events...
# 3. Acknowledge delivered eventscurl -s https://api.tipoff.dev/v1/actions/ack \ -H "Authorization: Bearer sk_your_source_key" \ -H "Content-Type: application/json" \ -d '{"event_ids": ["evt_xxx", "evt_yyy"]}'Unacknowledged events remain pending and will be returned on the next poll. The cursor advances per-source.
Source-initiated actions
Section titled “Source-initiated actions”Sources can also complete or cancel notifications programmatically using a source key (sk_). This is different from device-initiated actions (where a user taps a button):
# Complete a notification (removes it from devices)curl -s https://api.tipoff.dev/v1/notifications/task-123/actions \ -H "Authorization: Bearer sk_your_source_key" \ -H "Content-Type: application/json" \ -d '{"action": "complete"}'When a source-initiated action is posted:
- The notification is tombstoned on the server
- A silent push is sent to all devices to remove it
- A
CompletionEventis published so all delivery consumers (webhook, gRPC, SSE) see the completion
This is the mechanism behind the agent’s signal: "clear" mode — when a task is completed outside TipOff (e.g. via the Vikunja web UI), the adapter fires a source-initiated action to dismiss the notification from all devices.
Available source actions: complete, cancel.