# Opik alert notifications on your phone > Get a push notification when Opik sees trace errors or guardrail hits, or when cost, latency or feedback scores cross a threshold. - Company: Opik (https://www.justpush.io/recipes/opik) - Event: `trace:errors` (Trace alert) - Tags: AI, Monitoring - Install: https://studio.justpush.io/recipes/opik/alert - Web page: https://www.justpush.io/recipes/opik/alert ## Setup 1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL. 2. In Opik, open Production > Alerts and create an alert with destination type General. 3. Paste your webhook URL (shown in Studio after install) as the webhook URL and select the Trace alert events. 4. Use Test trigger to send the example payload, then save. 5. In Opik, open **Production โ†’ Alerts**, create an alert with destination type **General**, paste the URL above, and select the events you want: `trace:errors`, `trace:guardrails_triggered`, `trace:cost`, `trace:latency`, `trace:feedback_score` or `trace_thread:feedback_score`. 6. **Test trigger** sends Opik's example payload. Opik groups events that happen close together, so one push can cover several traces. 7. Opik can send a secret token as `Authorization: Bearer โ€ฆ`, but JustPush doesn't check it yet, so keep your endpoint URL private. ## Code Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing. ```js // Opik โ†’ alert // Fires when an Opik alert with a General webhook destination sees trace errors, // guardrail hits, or cost, latency or feedback score thresholds being crossed. // Docs: https://www.comet.com/docs/opik/production/alerts // Observability event types, how to label them, and how loud to be. const EVENTS = { "trace:errors": { emoji: "โŒ", priority: 1 }, "trace:guardrails_triggered": { emoji: "๐Ÿ›ก๏ธ", priority: 1 }, "trace:cost": { emoji: "๐Ÿ’ธ", priority: 1 }, "trace:latency": { emoji: "๐Ÿข", priority: 0 }, "trace:feedback_score": { emoji: "๐Ÿ‘Ž", priority: 0 }, "trace_thread:feedback_score": { emoji: "๐Ÿ‘Ž", priority: 0 }, } // Look a key up in one of the tables above, ignoring inherited names like "toString". function pick(table, key, fallback) { return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback } function handleRequest(request) { const body = request.body || {} const payload = body.payload ?? {} const event = pick(EVENTS, body.eventType, null) // Guard: only observability alerts; experiments and prompts have their own recipes. if (!event) { console.log(`Ignoring Opik "${body.eventType ?? "unknown"}" webhook`) return null } const name = body.alertName ?? payload.alertName ?? "Opik alert" // metadata is an object for threshold alerts and an array for per-trace events. const meta = payload.metadata const items = Array.isArray(meta) ? meta : [] let detail = null if (meta && !Array.isArray(meta) && meta.metric_value != null) { const metric = String(meta.metric_name ?? body.eventType).replace(/^trace(_thread)?:/, "").replace(/_/g, " ") detail = `${metric} ${meta.metric_value}` + (meta.threshold != null ? ` (threshold ${meta.threshold})` : "") + (meta.project_names ? ` ยท ${String(meta.project_names).split(",").join(", ")}` : "") } else if (items.length) { const first = items[0] ?? {} const what = first.error_info ? [first.error_info.exception_type, first.error_info.message].filter(Boolean).join(": ") : [first.name, first.result, first.details?.message].filter(Boolean).join(" โ€” ") const count = Number(payload.eventCount) || items.length detail = (what || first.name || "Trace") + (first.project_name ? ` ยท ${first.project_name}` : "") + (count > 1 ? ` (+${count - 1} more)` : "") } return { title: `${event.emoji} ${name}`, message: detail ?? payload.message ?? "Alert triggered", topic: "Opik", priority: event.priority, } } ``` ## Opik trace:errors webhook payload (sample) ```json { "id": "webhook-event-7f3a", "alertId": "9b1d2c3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e", "payload": { "alertId": "9b1d2c3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e", "message": "Alert 'Production errors': 2 trace:errors events aggregated", "eventIds": [ "event-id-1", "event-id-2" ], "metadata": [ { "id": "trace-uuid-1", "name": "handle_query", "tags": [ "production" ], "error_info": { "message": "Validation failed", "exception_type": "ValidationException" }, "project_name": "Support bot" }, { "id": "trace-uuid-2", "name": "handle_query", "tags": [ "production" ], "error_info": { "message": "LLM call timed out", "exception_type": "TimeoutError" }, "project_name": "Support bot" } ], "alertName": "Production errors", "eventType": "trace:errors", "userNames": [ "user@example.com" ], "eventCount": 2, "aggregationType": "consolidated" }, "alertName": "Production errors", "createdAt": "2026-09-29T10:30:00Z", "eventType": "trace:errors", "workspaceId": "workspace-uuid" } ``` ## FAQ ### Does this work on iPhone and Android? Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification. ### Do I need to write code? No. Install the recipe in Studio, paste your webhook URL into Opik and you are done. The code is there if you want to change the text, the sound or the buttons. ### Can I change what the notification says? Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save. ### What does it cost? JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.