NotiLens delivers real-time push notifications to your phone or team when tasks start, make progress, hit errors, or finish. No polling — instant alerts.
Get your NOTILENS_TOKEN and NOTILENS_SECRET from your topic settings at https://www.notilens.com.
All notifications are sent as a POST to the NotiLens webhook:
text
POST https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send
X-NOTILENS-KEY: $NOTILENS_SECRET
Content-Type: application/json
FieldTypeRequiredDescriptioneventstringyesEvent name, e.g. task.started, task.completed``titlestringyesShort heading shown on the notificationmessagestringyesNotification body texttypestringyesinfo | success | warning | urgent``agentstringyesName identifying the source (app, script, agent, etc.)task_idstringnoTask label for grouping related eventsrun_idstringnoUnique ID for this specific run, e.g. run_1714000000000_a3f2``is_actionablebooleannoSet true when the event needs human attentionimage_urlstringnoImage to display with the notificationopen_urlstringnoURL to open when the notification is tappeddownload_urlstringnoURL of a file to attach to the notificationtagsstringnoComma-separated tags, e.g. prod,backend``tsnumbernoUnix timestamp (seconds). Defaults to now.metaobjectnoMetrics, counters, timing, and any custom key-value pairs
Use these canonical event names for consistency across sources:
EventtypeWhen to firetask.queued``infoTask is queued before a worker picks it uptask.started``infoExecution beginstask.progress``infoMeaningful checkpoint during a long tasktask.paused``warningTask is pausing (waiting on I/O, rate limit, etc.)task.waiting``warningTask is blocked waiting for an external resourcetask.resumed``infoTask resumed after a pause or waittask.loop``warningSame step is repeating — possible looptask.retry``warningTask is being retried after a failuretask.error``urgentNon-fatal error occurred, task continuestask.completed``successTask finished successfullytask.failed``urgentTask failed — will not be retriedtask.timeout``urgentTask exceeded its time limittask.cancelled``warningTask was cancelled before completiontask.stopped``infoTask was stopped intentionally (not an error)task.terminated``urgentTask was forcibly terminatedinput.required``warningNeeds human input to continueinput.approved``successHuman approved the requestinput.rejected``warningHuman rejected the requestoutput.generated``successOutput produced (file, report, result, etc.)output.failed``urgentFailed to produce expected output
You may also use any custom event name appropriate to your workflow (e.g. order.placed, deploy.started, pipeline.complete).
Attach numeric or string metrics to any event's meta object. NotiLens surfaces these in the dashboard for filtering and analytics.
meta Fields#Timing (milliseconds):
KeyDescriptiontotal_duration_msWall-clock time from task start to nowactive_msTime actively running (excludes pauses and waits)queue_msTime spent in queue before task startedpause_msTotal time spent pausedwait_msTotal time spent waiting on external resources
Counters:
KeyDescriptionretry_countNumber of retries so farloop_countNumber of loop iterationserror_countNumber of non-fatal errors encounteredpause_countNumber of times the task pausedwait_countNumber of times the task waited
Custom metrics — include any domain-specific values:
json
"meta": {
"rows_processed": 4218,
"rows_failed": 3,
"tokens_used": 18400,
"model": "claude-opus-4-6",
"env": "production",
"region": "us-east-1"
}
Numeric metrics accumulate meaningfully when charted over time. Include them on task.completed and task.failed events at minimum.
run_id — Unique Run Identification#Generate a run_id at the start of each task run and include it on every event for that run. This allows NotiLens to correlate all events from the same execution even if task_id is reused across runs.
text
run_id format: run_{unix_ms}_{random_hex4}
example: run_1714000000000_a3f2
Fire task.loop when the same step is repeating. Include the loop count in meta.
bash
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.loop",
"title": "my-app | scraper | task.loop",
"message": "Same page returned 5 times — possible infinite loop.",
"type": "warning",
"agent": "my-app",
"task_id": "scraper",
"run_id": "run_1714000000000_a3f2",
"is_actionable": true,
"meta": {
"loop_count": 5
}
}'
bash
# 1. Queued
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.queued",
"title": "my-app | data-pipeline | task.queued",
"message": "Data pipeline job queued.",
"type": "info",
"agent": "my-app",
"task_id": "data-pipeline",
"run_id": "run_1714000000000_a3f2"
}'
# 2. Started
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.started",
"title": "my-app | data-pipeline | task.started",
"message": "Starting nightly data pipeline run.",
"type": "info",
"agent": "my-app",
"task_id": "data-pipeline",
"run_id": "run_1714000000000_a3f2",
"meta": { "queue_ms": 1240 }
}'
# 3. Completed with metrics
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.completed",
"title": "my-app | data-pipeline | task.completed",
"message": "Pipeline finished. Processed 4,218 records in 47s.",
"type": "success",
"agent": "my-app",
"task_id": "data-pipeline",
"run_id": "run_1714000000000_a3f2",
"meta": {
"total_duration_ms": 47200,
"active_ms": 45800,
"rows_processed": 4218,
"rows_failed": 0,
"env": "production"
}
}'
bash
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.failed",
"title": "my-app | data-pipeline | task.failed",
"message": "Database connection timed out after 3 retries.",
"type": "urgent",
"agent": "my-app",
"task_id": "data-pipeline",
"run_id": "run_1714000000000_a3f2",
"is_actionable": true,
"meta": {
"total_duration_ms": 92000,
"active_ms": 88000,
"retry_count": 3,
"error_count": 3,
"last_error": "connect ETIMEDOUT 10.0.0.5:5432"
}
}'
bash
# Pausing (e.g. hit rate limit)
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.paused",
"title": "my-app | api-sync | task.paused",
"message": "Rate limit hit — waiting 30s before resuming.",
"type": "warning",
"agent": "my-app",
"task_id": "api-sync",
"run_id": "run_1714000000000_b7c1",
"meta": { "pause_count": 1, "wait_reason": "rate_limit" }
}'
# Resuming
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "task.resumed",
"title": "my-app | api-sync | task.resumed",
"message": "Resuming after rate limit window.",
"type": "info",
"agent": "my-app",
"task_id": "api-sync",
"run_id": "run_1714000000000_b7c1",
"meta": { "pause_ms": 31200, "pause_count": 1 }
}'
bash
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "input.required",
"title": "my-app | approval needed",
"message": "About to delete 83 records. Please confirm.",
"type": "warning",
"agent": "my-app",
"is_actionable": true,
"open_url": "https://dashboard.example.com/approve/123"
}'
bash
curl -s -X POST "https://hook.notilens.com/webhook/$NOTILENS_TOKEN/send" \
-H "Content-Type: application/json" \
-H "X-NOTILENS-KEY: $NOTILENS_SECRET" \
-d '{
"event": "output.generated",
"title": "my-app | report-gen | output.generated",
"message": "Monthly report ready. 24 pages, 3 charts.",
"type": "success",
"agent": "my-app",
"task_id": "report-gen",
"download_url": "https://storage.example.com/reports/2026-04.pdf",
"meta": {
"pages": 24,
"charts": 3,
"total_duration_ms": 18400
}
}'
task.started when beginning a significant task so the user knows work has begun.task.completed or task.failed at every terminal state — never leave a started task without a closing event.run_id at task start ( run_{unix_ms}_{random_hex4}) and include it on every event for that run.meta on terminal events ( task.completed, task.failed, task.timeout) — total_duration_ms and active_ms at minimum.meta whenever they are non-zero: retry_count, error_count, loop_count, pause_count, wait_count.input.required with is_actionable: true whenever a human decision is needed before continuing.task.loop when the same logical step is repeating. Include loop_count in meta.message concise and informative — include counts, durations, or key values (e.g. "Processed 1,240 rows in 3.2s — 2 errors").task_id consistently across all events for the same logical task so NotiLens can group them.task.progress more than once every few seconds for fast-running tasks.Set these environment variables before running:
bash
export NOTILENS_TOKEN=your_topic_token
export NOTILENS_SECRET=your_topic_secret
Both are found in your topic settings at https://www.notilens.com.