Product

Monitor

Monitor a business-critical process like checkout or signup. Your backend reports each step with one HTTP call; Releaseo shows a live funnel and alerts Slack or Discord on failure or completion.

Releaseo Monitor observes the health of a business-critical process — checkout, signup, password reset, a data import — the steps your own backend controls. Declare the monitor’s steps once in the dashboard, then call one HTTP endpoint from your backend as each step happens. Releaseo turns those calls into a live step funnel, a searchable run timeline, and a Slack or Discord alert when a step fails or a run completes.

Monitor is not an APM or request-tracing tool, and it is not session or user tracking. There is no SDK involved and nothing runs in a visitor’s browser — Releaseo only knows what your backend explicitly reports for the steps you declared, nothing more.

Quickstart

1. Create a monitor from a template

Open Dashboard → Monitor and start from a template — Signup, Checkout & payment, Onboarding provisioning, Password reset, Subscription change, or Data import — or start blank and define your own steps. Each step gets a key (for example payment_authorized) and a label. Mark one step terminal; a run counts as completed when that step reports success.

2. Generate a server key

Monitor events are authenticated with a dedicated secret key, separate from the browser-safe SDK key used elsewhere in Releaseo. Open Settings → Environment → Server Runtime and generate one. It looks like sk_live_… and is shown in full so you can copy it into your backend’s own configuration.

3. Send your first event

Call the endpoint from your backend as the step happens. Here is the first step of the Checkout & payment template:

curl -X POST https://api.releaseo.io/api/public/flows/events \
  -H "x-project-key: $RELEASEO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "flow": "checkout-payment",
    "ref": "req_8f3a",
    "step": "started",
    "status": "success",
    "metadata": {
      "source": "checkout-api",
      "referrerHost": "app.example.com",
      "referrerPath": "/checkout",
      "user.id": "user_42"
    }
  }'

ref identifies one execution of the monitor — your own request id, order id, or job id works well. Send every step of that execution with the same ref; Releaseo groups them into a single run.

4. Watch it fill in

Open the monitor’s Overview tab. The step funnel fills in as events arrive, the run appears in the recent-runs list, and — if you send a status: "failure" event — it shows up in the failure feed too. Events start appearing as soon as you send them; wiring up Slack/Discord alerts is a separate opt-in step, covered below.

API reference

Endpoint and auth

POST /public/flows/events

Every request carries the server key from step 2 in the x-project-key header. There is no project id anywhere in the URL — the key itself resolves the project.

ResponseMeaning
401 UnauthorizedThe x-project-key header is missing, or the key value is unknown.
403 ForbiddenThe key exists but has been disabled.
403 { code: "server_key_required" }The key is valid and enabled, but is not a server key — “This endpoint requires a server-scope project key, not a browser SDK key.”
202 { "accepted": <count> }The batch passed validation and was queued.

Payload

Send one event object, or a JSON array of up to 100 for a batch:

{
  "flow": "checkout-payment",
  "ref": "req_8f3a",
  "step": "payment_authorized",
  "status": "success",
  "error": "Optional error text",
  "message": "Optional free-text note, up to 512 characters",
  "metadata": { "source": "checkout-worker", "region": "eu-west-1" },
  "env": "production",
  "occurredAt": "2026-01-01T00:00:00.000Z"
}
FieldTypeRequiredNotes
flowstringYesTechnical key of an enabled monitor in this project.
refstringYesUp to 128 characters. Identifies one run; reusing a ref merges into the same run instead of starting a new one.
stepstringYesKey of a declared step on that monitor.
status"success" | "failure" | "pending"Yespending means the step was reached but has not reported terminal success or failure yet. It is recorded as reached, but does not count as a completion or failure and emits no Monitor alert.
errorstringNoUp to 2000 characters. Shown verbatim in the run timeline and failure feed.
messagestringNoUp to 512 characters. A short free-text note, shown in the run timeline and — on a failed step — forwarded to Slack/Discord alerts.
metadataflat object of stringsNoUp to 24 entries; keys up to 64 characters (letters, digits, ., _, -); values up to 512 characters. String values only — no nested objects, arrays, numbers, booleans, or null. The keys __proto__, constructor, and prototype are rejected. Visible only in the dashboard’s run details and timeline; never sent to Slack or Discord.
envstringNoLowercase letters, digits, ., _, -, up to 32 characters. Defaults to production; any value you send is lowercased automatically.
occurredAtstring (ISO-8601)NoMust be within 7 days of the current time. Defaults to the time Releaseo received the event. Send the original timestamp on a retried request — see batching and retries.

Use pending when your backend has reached a step but its terminal outcome is still unknown. For example, a payment worker can report { "step": "payment_authorized", "status": "pending" } before reporting success or failure again with the same ref and step when the outcome is known.

Server-side examples

All examples use the server-only RELEASEO_SERVER_KEY environment variable and the same flat, caller-supplied metadata. Never send raw URL query strings or fragments, email addresses, IP addresses, tokens, or cookies in metadata.

PHP (native cURL)

<?php

$event = [
    'flow' => 'checkout-payment', 'ref' => $orderId, 'step' => 'started', 'status' => 'success',
    'metadata' => [
        'source' => 'checkout-api', 'referrerHost' => 'app.example.com',
        'referrerPath' => '/checkout', 'user.id' => $userId,
    ],
];

$serverKey = getenv('RELEASEO_SERVER_KEY');
if ($serverKey === false || $serverKey === '') throw new RuntimeException('RELEASEO_SERVER_KEY is required');

$curl = curl_init('https://api.releaseo.io/api/public/flows/events');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'x-project-key: ' . $serverKey],
    CURLOPT_POSTFIELDS => json_encode($event, JSON_THROW_ON_ERROR),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($curl);
if ($response === false) {
    $message = curl_error($curl);
    curl_close($curl);
    throw new RuntimeException($message);
}
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($status < 200 || $status >= 300) {
    throw new RuntimeException('Releaseo Monitor event failed with HTTP ' . $status);
}

Node.js (fetch)

const serverKey = process.env.RELEASEO_SERVER_KEY;
if (!serverKey) throw new Error("RELEASEO_SERVER_KEY is required");

const response = await fetch(
  "https://api.releaseo.io/api/public/flows/events",
  {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-project-key": serverKey,
    },
    body: JSON.stringify({
      flow: "checkout-payment",
      ref: orderId,
      step: "started",
      status: "success",
      metadata: {
        source: "checkout-api",
        referrerHost: "app.example.com",
        referrerPath: "/checkout",
        "user.id": userId,
      },
    }),
  },
);
if (!response.ok)
  throw new Error(`Releaseo Monitor event failed: ${response.status}`);

Elixir (Req)

response =
  Req.post!("https://api.releaseo.io/api/public/flows/events",
    headers: [{"x-project-key", System.fetch_env!("RELEASEO_SERVER_KEY")}],
    json: %{
      flow: "checkout-payment", ref: order_id, step: "started", status: "success",
      metadata: %{
        "source" => "checkout-api", "referrerHost" => "app.example.com",
        "referrerPath" => "/checkout", "user.id" => user_id
      }
    }
  )

C# (HttpClient)

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;

using var httpClient = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.releaseo.io/api/public/flows/events");
request.Headers.Add("x-project-key", Environment.GetEnvironmentVariable("RELEASEO_SERVER_KEY")
    ?? throw new InvalidOperationException("RELEASEO_SERVER_KEY is required"));
request.Content = JsonContent.Create(new {
    flow = "checkout-payment", @ref = orderId, step = "started", status = "success",
    metadata = new Dictionary<string, string> {
        ["source"] = "checkout-api", ["referrerHost"] = "app.example.com",
        ["referrerPath"] = "/checkout", ["user.id"] = userId,
    },
});
using var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();

Rust (reqwest)

use serde_json::json;

let response = reqwest::Client::new()
    .post("https://api.releaseo.io/api/public/flows/events")
    .header("x-project-key", std::env::var("RELEASEO_SERVER_KEY")?)
    .json(&json!({
        "flow": "checkout-payment", "ref": order_id, "step": "started", "status": "success",
        "metadata": {
            "source": "checkout-api", "referrerHost": "app.example.com",
            "referrerPath": "/checkout", "user.id": user_id,
        }
    }))
    .send()
    .await?;
response.error_for_status()?;

Batching and retries

Send a single object for one event, or a JSON array for up to 100 in one request. Validation runs against the whole batch: the first invalid event — by array position — rejects the entire request with a named error below. Nothing is partially accepted.

A 202 means the batch passed validation and was queued — not that every event is immediately queryable in the dashboard (usually a matter of seconds). Events are accepted at least once: if your backend retries a request after a timeout, send the retried events with the same occurredAt as the original attempt. Releaseo hashes occurredAt into the alert delivery id for step-failed and run-completed alerts, so a retry does not send a second Slack or Discord message for the same event. A duplicate event for the same ref and step is otherwise harmless to the monitor’s health view.

Error codes

Every 400 response is { "code": "...", "message": "..." }. For array input, the message names the offending index (for example, "...at index 1").

CodeMeaning
empty_batchThe array is empty.
batch_too_largeMore than 100 events in one request.
unknown_flowflow doesn’t match an enabled monitor in this project.
unknown_stepstep doesn’t match a declared step on that monitor.
invalid_statusstatus isn’t exactly "success", "failure", or "pending".
missing_refref is missing or empty.
ref_too_longref is over 128 characters.
invalid_errorerror isn’t a string.
error_too_longerror is over 2000 characters.
invalid_messagemessage isn’t a string, or is over 512 characters.
invalid_metadatametadata fails any rule — not an object, over 24 entries, a key that doesn’t match the allowed pattern or is one of the reserved names (__proto__, constructor, prototype), or a value that isn’t a string or is too long.
invalid_envenv isn’t a string, or doesn’t match the allowed pattern.
invalid_occurred_atoccurredAt isn’t a valid ISO-8601 string.
occurred_at_out_of_rangeoccurredAt is more than 7 days from now.

A null byte anywhere in the request body — inside error, message, a metadata value, or any other string — is rejected before any of the codes above run, with a generic 400 Input contains a forbidden null byte. This is a whole-request input rule, not a named per-field code.

Alerts

Monitor can notify Slack or Discord on two events:

EventFiresTurn it on
Monitor step failedAt most once every 15 minutes per step — edge-triggered on a status: "failure" event for that step.Bind a Slack or Discord destination to it.
Monitor run completedEvery time a run’s terminal step reports success.Turn on the monitor’s own “Alert when a run completes” switch, and bind a destination.

Both events are opt-in, the same way every other Releaseo event is: open Dashboard → Events → Notification Triggers, find “Monitor step failed” or “Monitor run completed”, and select the Slack channels or Discord destinations that should receive it. Each monitor’s own page also has an Alerts card with a shortcut straight into Notification Triggers, scoped to these two events.

A step can also be muted individually from the monitor’s step editor — a muted step’s failures never alert, and never spend the 15-minute cooldown window.

Folders, owners, and routing

Folders are an operational layer, not just a visual label. They let you group related monitors, assign one project member as the operational owner, and narrow where that folder’s alerts may be delivered. Moving a monitor between folders never changes its integration key or run history.

  • Operational owner — receives the folder’s dashboard failure notifications. If that member loses project access, Releaseo safely falls back to the project’s authorized notification recipients.
  • Inherit project routing — uses the Slack and Discord destinations already selected in Notification Triggers.
  • Only selected destinations — restricts delivery to the folder’s chosen Slack and Discord destinations. It never enables a trigger or bypasses an existing subscription; both layers must allow the event.

If a saved destination becomes unavailable, open Folder settings to repair or remove it. Releaseo preserves that saved choice during unrelated folder edits instead of silently replacing the route.

Custom messages

Both events use the same notification composer as every other Releaseo event trigger. You can:

  • turn the title or description on or off;
  • replace either one with your own static copy (leave the field blank to restore the Releaseo default); and
  • add an optional Additional message with dynamic fields.

Title and description overrides are saved per trigger and destination. If you edit Monitor step failed for two selected Slack channels, for example, the new copy applies to every Monitor step-failed alert sent to those channels — not only to the monitor page from which you opened the dialog.

The Additional message can use these fields:

  • Monitor step failedflow_name, step_label, error_message, message, run_ref, env
  • Monitor run completedflow_name, run_ref, env

Test your alert wiring

Every step in a monitor’s funnel has a copy button in the dashboard. The default click copies a ready-to-run success example for that exact step; its menu also offers “Copy failure example” — a ready-to-run curl with a realistic error and message that triggers the real step-failed alert (subject to the 15-minute cooldown) if you run it. Use it to confirm your Slack or Discord destination before relying on it in production.

FAQ

Does the server key expire?

No. It stays valid until you rotate it from Settings → Environment → Server Runtime. Rotating disables the previous key immediately.

What does a run look like when it fails?

Open a run from the failure feed or the recent-runs list to see its timeline. Steps show in the order they occurred; the failed step’s error and message render as-is, plus any metadata you sent, collapsed behind a disclosure once there are more than a few entries.

Do concurrent users create separate runs?

Yes. Each ref is its own run. Three visitors going through checkout at the same time produce three refs and three independent timelines in the funnel.

How long is data kept?

90 days, for every field — error, message, and metadata included.

Can staging and production share a monitor?

Yes, with separate env values. Send env: "staging" from your staging backend and it stays out of the dashboard’s default (production) view. env is lowercased automatically and defaults to production when omitted.

Next steps

  • Integrations — connect the Slack or Discord destinations your Notification Triggers deliver to.
  • Backend contract — service ownership and other public API surfaces.
Was this page helpful?