Skip to content

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:

MethodRequires agent?Best forExample
gRPC AgentYesAlways-on local delivery with YAML adapter routingDonetick, Vikunja task completion
SSENoWorkflow engines, EventSource clientsn8n SSE trigger node
WebhookNoDirect HTTP callbacks (HMAC-signed)Custom endpoints, serverless functions
PollingNoSimple cron-based integrationsShell scripts, cron jobs

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"
}

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.

Terminal window
TIPOFF_AGENT_API_KEY=sk_your_source_key \
TIPOFF_AGENT_GRPC_URL=grpc.tipoff.dev:443 \
TIPOFF_AGENT_ADAPTERS_DIR=./adapters \
./tipoff-agent

The 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.

Terminal window
curl -N https://api.tipoff.dev/v1/actions/stream \
-H "Authorization: Bearer sk_your_source_key"

Events arrive as:

id: 1713700000000-0
event: action
data: {"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.


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:

Terminal window
# Verify the signature
echo -n "$REQUEST_BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -hex

Example — verifying in a shell script:

Terminal window
EXPECTED=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "whsec_your_secret" -hex | awk '{print $2}')
if [ "$X_TIPOFF_SIGNATURE" = "$EXPECTED" ]; then
echo "Valid signature"
fi

The webhook_secret is a per-source field set when creating the source. It is never sent over the wire — only used to compute signatures.


For simple cron-based integrations, poll GET /v1/actions/pending with a cursor and acknowledge events via POST /v1/actions/ack.

Terminal window
# 1. Poll for pending events
curl -s https://api.tipoff.dev/v1/actions/pending \
-H "Authorization: Bearer sk_your_source_key" | jq .
# 2. Process events...
# 3. Acknowledge delivered events
curl -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.


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):

Terminal window
# 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:

  1. The notification is tombstoned on the server
  2. A silent push is sent to all devices to remove it
  3. A CompletionEvent is 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.