> ## Documentation Index
> Fetch the complete documentation index at: https://kb.mspprocess.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Example: build an uptime monitor

> Check websites and portals every five minutes, email your service desk when one goes down or recovers, and show their status in an internal app.

Check a list of URLs every five minutes, email your service desk when one goes down or recovers, and show every service's status on an internal app page. It's a trimmed-down version of the uptime monitor in our own test organisation.

<video controls preload="none" playsInline poster="/images/videos/studio-uptime-monitor.jpg" src="https://mintcdn.com/triggr/GxrCQFDFNLmEMyJ1/images/videos/studio-uptime-monitor.mp4?fit=max&auto=format&n=GxrCQFDFNLmEMyJ1&q=85&s=0820fe196c90f90cc53fed0de6175ae5" />

Build an uptime monitor: the database, the four workflows, the Status page, the live app and the schedule (1:25).

![An uptime Status page reading All systems operational, with tiles for 4 services, 4 operational, 0 down and 99.97% uptime over 30 days, and a Services list showing Client portal, Status page, Remote access gateway and Ticket portal with their example.com-style URLs, a 30-day bar of daily results, uptime and average response time, each marked Operational.](https://mintcdn.com/triggr/XztmSfsoAUpshfIO/images/studio/internal-apps/uptime-monitor-status.png?fit=max\&auto=format\&n=XztmSfsoAUpshfIO\&q=85\&s=4b3cac7d04035f4dd99205f2ca992eba)

The full uptime monitor in our test organisation. The page you build here shows the same services in a simpler table.

You need a PostgreSQL database that Triggr can reach over the internet, with [Triggr's IP addresses](https://kb.mspprocess.com/security/ip-allowlist.md) allowed through its firewall.

## How it works

```mermaid theme={null}
flowchart TD
  cron(["Cron, every 5 minutes"]) --> dispatch["Uptime - Dispatch Checks"]
  button(["Check now on the page"]) -- "triggr.runWorkflow" --> dispatch
  dispatch -- "one run per monitor" --> check["Uptime - Check Monitor"]
  check -- "waits for" --> probe["Uptime - Probe Endpoint"]
  probe -- "GET" --> site(("Monitored URL"))
  check -- "saves the result" --> db[("Your PostgreSQL database")]
  check -- "went down or recovered" --> email(["Email to your service desk"])
  page(["The page loads"]) -- "webhook with ?await=true" --> status["Uptime - Status"]
  status -- "reads" --> db
```

Dispatch Checks reads the monitors from your database and starts Check Monitor for each one without waiting. Check Monitor runs Probe Endpoint and waits for its answer: a site that doesn't answer at all fails only the probe's run, recorded as No response. It then saves the result, marks the monitor down after two failures in a row and emails when it goes down or comes back. The page can't read your database, so it calls Status through its webhook, and its Check now button starts a sweep with [`triggr.runWorkflow`](https://kb.mspprocess.com/studio/internal-apps/sdk#run-a-workflow).

## Build it

**1. Create the tables**

In your database's own SQL editor, run this, with one `uptime_monitors` row per site to watch.

```sql theme={null}
CREATE TABLE uptime_monitors (
  id              serial PRIMARY KEY,
  name            text NOT NULL,
  url             text NOT NULL,
  status          text NOT NULL DEFAULT 'unknown',
  failures        int  NOT NULL DEFAULT 0,
  last_checked_at timestamptz,
  last_error      text NOT NULL DEFAULT ''
);

CREATE TABLE uptime_checks (
  monitor_id int NOT NULL REFERENCES uptime_monitors (id) ON DELETE CASCADE,
  checked_at timestamptz NOT NULL DEFAULT now(),
  ok         boolean NOT NULL
);
CREATE INDEX ON uptime_checks (monitor_id, checked_at);

INSERT INTO uptime_monitors (name, url) VALUES
  ('Client portal', 'https://www.example.com'),
  ('Status page',   'https://status.example.org');
```

**2. Connect the database**

In Data > Databases, click **New Connection > Connection String**, paste your connection string and click Create Connection, then click Test Connection on its row (see [Databases](https://kb.mspprocess.com/studio/data/databases.md)).

**3. Import Uptime - Probe Endpoint**

In Workflows, click **Import Workflow**, paste this and click Import Workflow. It opens in the designer: click Save.

![The Import Workflow dialog with Paste JSON and Upload File tabs, an empty box for a workflow execution definition JSON, and Cancel and Import Workflow buttons.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/import-workflow.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=952a0b33d47bc4a6a8a38a7f02767ff9)

```json Uptime - Probe Endpoint theme={null}
{
  "id": "set-on-import",
  "name": "Uptime - Probe Endpoint",
  "steps": [
    {"id": "trigger-1", "type": "trigger", "label": "Trigger", "data": {"triggers": []}, "next": ["request"]},
    {"id": "request", "type": "http_request", "label": "Request", "data": {"method": "GET", "url": "{{ ctx.params.url }}", "bodyType": "none", "allowNon2xxStatus": true, "allowInsecureTLS": false}, "next": ["finish"]},
    {"id": "finish", "type": "finish_workflow", "label": "Finish Workflow", "data": {"outcome": "success", "comment": "{{ ctx.steps['Request'].status }}"}, "next": []}
  ],
  "triggers": [],
  "metadata": { "rootNodes": ["trigger-1"] }
}
```

Click Request to see its settings: Allow Non-2xx Status is on, so an error page still counts as an answer, and Finish Workflow returns the HTTP status as its Comment.

![The Request step: Method GET, URL set to ctx.params.url, no headers, Body Type None, Allow Non-2xx Status ticked and Allow Insecure TLS unticked.](https://mintcdn.com/triggr/XztmSfsoAUpshfIO/images/studio/internal-apps/probe-request-step.png?fit=max\&auto=format\&n=XztmSfsoAUpshfIO\&q=85\&s=62faefd22b18f0bd438f04f507093799)

**4. Import Uptime - Check Monitor**

Import this the same way.

```json Uptime - Check Monitor theme={null}
{
  "id": "set-on-import",
  "name": "Uptime - Check Monitor",
  "steps": [
    {"id": "trigger-1", "type": "trigger", "label": "Trigger", "data": {"triggers": []}, "next": ["probe"]},
    {"id": "probe", "type": "trigger_workflow", "label": "Probe", "data": {"workflow": "", "awaitCompletion": true, "params": "{ \"url\": {{ ctx.params.url | json }} }"}, "next": ["evaluate"]},
    {"id": "evaluate", "type": "code", "label": "Evaluate", "data": {"javascript": "// A failed probe run means no response: DNS, refused, TLS error or the 30-second timeout.\nvar m = ctx.params;\nvar probe = ctx.steps['Probe'];\nvar code = probe.status === 'completed' ? Number(probe.finishNodeOutput.comment) : 0;\nvar ok = code >= 200 && code < 400;\n\n// Two failures in a row before it counts as down, so one blip doesn't page anyone.\nvar failures = ok ? 0 : Number(m.failures) + 1;\nvar status = ok ? 'up' : (failures >= 2 ? 'down' : m.status);\nvar error = ok ? '' : (code ? 'HTTP ' + code : 'No response');\n\nvar alert = 'none';\nif (status === 'down' && m.status !== 'down') alert = 'down';\nif (status === 'up' && m.status === 'down') alert = 'up';\n\nfunction q(s) { return \"'\" + String(s).replace(/'/g, \"''\") + \"'\"; }\nvar id = parseInt(m.id, 10);\n\nreturn {\n  alert: alert,\n  error: error,\n  sql: 'WITH c AS (INSERT INTO uptime_checks (monitor_id, ok) VALUES (' + id + ', ' + ok + ')) ' +\n       'UPDATE uptime_monitors SET status = ' + q(status) + ', failures = ' + failures +\n       ', last_checked_at = now(), last_error = ' + q(error) + ' WHERE id = ' + id\n};"}, "next": ["save-result"]},
    {"id": "save-result", "type": "execute_sql", "label": "Save Result", "data": {"database": "", "sql": "{{ ctx.steps['Evaluate'].sql }}"}, "next": ["needs-alert"]},
    {"id": "needs-alert", "type": "condition", "label": "Needs Alert", "data": {"conditionLanguage": "javascript", "conditions": [{"id": "down", "label": "Went down", "condition": "ctx.steps['Evaluate'].alert === 'down'"}, {"id": "up", "label": "Recovered", "condition": "ctx.steps['Evaluate'].alert === 'up'"}], "defaultLabel": "No change"}, "next": ["down-alert:down", "recovery-alert:up", "finish:default"]},
    {"id": "down-alert", "type": "send_email", "label": "Down Alert", "data": {"to": "servicedesk@yourmsp.com", "subject": "DOWN: {{ ctx.params.name }}", "body": "{{ ctx.params.url }} has failed two checks in a row: {{ ctx.steps['Evaluate'].error }}."}, "next": ["finish"]},
    {"id": "recovery-alert", "type": "send_email", "label": "Recovery Alert", "data": {"to": "servicedesk@yourmsp.com", "subject": "RECOVERED: {{ ctx.params.name }}", "body": "{{ ctx.params.url }} is responding again."}, "next": ["finish"]},
    {"id": "finish", "type": "finish_workflow", "label": "Finish Workflow", "data": {"outcome": "success", "comment": ""}, "next": []}
  ],
  "triggers": [],
  "metadata": { "rootNodes": ["trigger-1"] }
}
```

![A workflow canvas: Trigger, Probe, Evaluate and Save Result in a line, then Needs Alert branching to Down Alert, Recovery Alert and straight to Finish Workflow.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/check-monitor-canvas.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=a22eb83cf6dc03168c3a3f24cebe3195)

Uptime - Check Monitor after import.

Click **Probe** and choose Uptime - Probe Endpoint as its Workflow, click Save Result and choose your database, change To on Down Alert and Recovery Alert to your service desk's address, then click Save.

![The Probe step: Workflow Uptime - Probe Endpoint, Await Completion ticked, and Parameters passing url from ctx.params.url with the json filter.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/check-probe-step.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=27c1260e9729f949e111318f299936e9)

Evaluate counts a status from 200 to 399 as up and the second failure in a row as down; Needs Alert only emails on a change. To alert on the first failure, change `failures >= 2` to `failures >= 1` in Evaluate.

**5. Import Uptime - Dispatch Checks**

Import this.

```json Uptime - Dispatch Checks theme={null}
{
  "id": "set-on-import",
  "name": "Uptime - Dispatch Checks",
  "steps": [
    {"id": "trigger-1", "type": "trigger", "label": "Trigger", "data": {"triggers": []}, "next": ["find-monitors"]},
    {"id": "find-monitors", "type": "execute_sql", "label": "Find Monitors", "data": {"database": "", "sql": "SELECT id, name, url, status, failures FROM uptime_monitors ORDER BY id"}, "next": ["each-monitor"]},
    {"id": "each-monitor", "type": "for_loop", "label": "Each Monitor", "data": {"expression": "ctx.steps['Find Monitors'].data", "variableName": "monitor"}, "next": ["check:body", "finish:complete"]},
    {"id": "check", "type": "trigger_workflow", "label": "Check", "data": {"workflow": "", "awaitCompletion": false, "params": "{{ ctx.monitor | json }}"}, "next": ["end-loop"]},
    {"id": "end-loop", "type": "end_for_loop", "label": "End For Loop", "data": {}, "next": []},
    {"id": "finish", "type": "finish_workflow", "label": "Finish Workflow", "data": {"outcome": "success", "comment": ""}, "next": []}
  ],
  "triggers": [],
  "metadata": { "rootNodes": ["trigger-1"] }
}
```

![A workflow canvas: Trigger, Find Monitors, then an Each Monitor loop whose body runs Check and End For Loop, and whose completion leads to Finish Workflow.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/dispatch-canvas.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=73d57dd64f2d84d62ad7f750077d013f)

Uptime - Dispatch Checks after import.

Click **Find Monitors** and choose your database, click Check and choose Uptime - Check Monitor as its Workflow, then click Save. You'll add its schedule once you've tested it.

![The Find Monitors Execute SQL step: Database MSP data and a SQL Query selecting id, name, url, status and failures from uptime\_monitors ordered by id.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/dispatch-find-monitors-step.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=0a38a1c704582c9c6794b1ff869fb973)

**6. Import Uptime - Status**

Import this, click **Load Status**, choose your database and click Save. Its Trigger already holds a Webhook trigger.

```json Uptime - Status theme={null}
{
  "id": "set-on-import",
  "name": "Uptime - Status",
  "steps": [
    {"id": "trigger-1", "type": "trigger", "label": "Trigger", "data": {"triggers": [{"id": "trigger-webhook", "type": "webhook", "formId": "", "chron": "", "webhookEndpoint": null}]}, "next": ["load-status"]},
    {"id": "load-status", "type": "execute_sql", "label": "Load Status", "data": {"database": "", "sql": "SELECT COALESCE(json_agg(json_build_object(\n  'name', m.name,\n  'status', m.status,\n  'lastCheckedAt', m.last_checked_at,\n  'lastError', m.last_error,\n  'uptime', (SELECT round(100.0 * count(*) FILTER (WHERE c.ok) / NULLIF(count(*), 0), 2)\n             FROM uptime_checks c\n             WHERE c.monitor_id = m.id AND c.checked_at > now() - interval '30 days')\n) ORDER BY m.name), '[]')::text AS monitors\nFROM uptime_monitors m"}, "next": ["finish"]},
    {"id": "finish", "type": "finish_workflow", "label": "Finish Workflow", "data": {"outcome": "success", "comment": "{{ ctx.steps['Load Status'].data[0].monitors }}"}, "next": []}
  ],
  "triggers": [{"id": "trigger-webhook", "type": "webhook", "formId": "", "chron": "", "webhookEndpoint": null}],
  "metadata": { "rootNodes": ["trigger-1"] }
}
```

It returns each monitor's name, status, 30-day uptime, last check and last error as JSON, without the URLs: anyone with a webhook's URL can run it and read what it returns.

**7. Add the app page**

In Internal Apps, click **New App**, name it `Uptime` and click Create App. Click Design on its row, click Add Page, name it `Status` and click Create Page.

Paste this page and replace `STATUS_WORKFLOW_ID` and `DISPATCH_WORKFLOW_ID` with the IDs of Uptime - Status and Uptime - Dispatch Checks, from each workflow's address in Studio (`app.triggr-app.com/workflows/<id>/design`). Click Save, then **Preview**.

```html Status page theme={null}
<!doctype html>
<html>
<head>
<style>
  body { margin: 0; padding: 24px; background: #09090b; color: #fafafa; font: 14px -apple-system, "Segoe UI", Roboto, sans-serif; }
  header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
  h1 { font-size: 18px; margin: 0; flex: 1; }
  button { background: #6366f1; color: #fff; border: 0; border-radius: 8px; padding: 8px 16px; font: inherit; cursor: pointer; }
  button:disabled { opacity: .5; }
  #summary { padding: 16px; border-radius: 10px; margin-bottom: 16px; font-weight: 600; }
  .ok { background: rgba(34,197,94,.15); color: #22c55e; }
  .bad { background: rgba(239,68,68,.15); color: #ef4444; }
  table { width: 100%; border-collapse: collapse; background: #18181b; border-radius: 10px; }
  th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid #27272a; }
  th { color: #a1a1aa; font-weight: 500; }
  .pill { padding: 2px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #27272a; color: #a1a1aa; }
  .pill.up { background: rgba(34,197,94,.15); color: #22c55e; }
  .pill.down { background: rgba(239,68,68,.15); color: #ef4444; }
  #message { color: #a1a1aa; margin-top: 12px; }
</style>
</head>
<body>
  <header>
    <h1>Uptime</h1>
    <button id="check">Check now</button>
  </header>
  <div id="summary">Loading…</div>
  <table>
    <thead><tr><th>Service</th><th>Status</th><th>Uptime (30 days)</th><th>Last checked</th><th>Last error</th></tr></thead>
    <tbody id="rows"></tbody>
  </table>
  <p id="message"></p>

<script>
  // Replace these with your workflows' IDs (from the address bar in the designer).
  var STATUS_URL = 'https://api.triggr-app.com/telescope/webhook/STATUS_WORKFLOW_ID?await=true&timeout=30';
  var DISPATCH_WORKFLOW_ID = 'DISPATCH_WORKFLOW_ID';

  function esc(s) {
    return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  // Runs Uptime - Status through its webhook and returns the monitors it lists.
  async function loadMonitors() {
    var res = await fetch(STATUS_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
    if (!res.ok) throw new Error('Status request failed (HTTP ' + res.status + ')');
    var run = await res.json();
    if (run.status !== 'completed') throw new Error('Uptime - Status ' + run.status + ': ' + (run.error || ''));
    return JSON.parse(run.output.comment);
  }

  async function refresh() {
    var summary = document.getElementById('summary');
    try {
      var monitors = await loadMonitors();
      var down = monitors.filter(function (m) { return m.status === 'down'; });
      summary.className = down.length ? 'bad' : 'ok';
      summary.textContent = down.length
        ? 'Down: ' + down.map(function (m) { return m.name; }).join(', ')
        : 'All systems operational';
      document.getElementById('rows').innerHTML = monitors.map(function (m) {
        return '<tr><td>' + esc(m.name) + '</td>' +
          '<td><span class="pill ' + esc(m.status) + '">' + esc(m.status) + '</span></td>' +
          '<td>' + (m.uptime == null ? '-' : esc(m.uptime) + '%') + '</td>' +
          '<td>' + (m.lastCheckedAt ? new Date(m.lastCheckedAt).toLocaleString() : 'Never') + '</td>' +
          '<td>' + esc(m.lastError) + '</td></tr>';
      }).join('');
    } catch (err) {
      summary.className = 'bad';
      summary.textContent = err.message;
    }
  }

  // Starts a sweep now. The results arrive about half a minute later.
  document.getElementById('check').addEventListener('click', async function () {
    var button = this;
    var message = document.getElementById('message');
    button.disabled = true;
    try {
      await triggr.runWorkflow(DISPATCH_WORKFLOW_ID, {});
      message.textContent = 'Checking every monitor. This page refreshes in 30 seconds.';
      setTimeout(function () { refresh(); message.textContent = ''; button.disabled = false; }, 30000);
    } catch (err) {
      message.textContent = err.message;
      button.disabled = false;
    }
  });

  refresh();
  setInterval(refresh, 300000); // every 5 minutes, the same as the sweep
</script>
</body>
</html>
```

![The Uptime app editor with the Status page's HTML on the left and a preview on the right: an Uptime heading with a Check now button, a red banner reading Down: Ticket portal, and a table of Client portal, Remote access gateway, Status page and Ticket portal with status pills, 30-day uptime, last checked time and the last error HTTP 503.](https://mintcdn.com/triggr/XztmSfsoAUpshfIO/images/studio/internal-apps/uptime-designer-preview.png?fit=max\&auto=format\&n=XztmSfsoAUpshfIO\&q=85\&s=ca742ddfefba79226f51bcf5621714f6)

Preview loads the table from Uptime - Status.

**8. Let the page start sweeps**

Click **Settings**, leave Authentication Type as Internal, tick Uptime - Dispatch Checks under Workflows pages can run and click Save Settings.

![The App Authentication panel with Internal selected, Hide top bar off, an Internal-only note that the app is staff-only, Allow file uploads off, and Uptime - Dispatch Checks ticked under Workflows pages can run above Uptime - Check Monitor, Uptime - Probe Endpoint and Uptime - Status.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/app-settings-uptime.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=303b361bfc0c70b7c0767f6a75c7a109)

Your team opens the live app at `https://app.triggr-app.com/telescope/apps/<org-id>/<app-id>` with their staff sign-in (**Support > Copy Org ID** in the top bar gives the first ID; the second is in the designer's address bar). Open on Internal Apps shows the page without the SDK, so Check now only works on that link.

## Test it

**1. Probe a URL**

Open Uptime - Probe Endpoint, click **Manual Trigger**, enter `{ "url": "https://www.example.com" }` and click Trigger Workflow. In the run, Finish Workflow's output has `comment: "200"`.

![The Trigger Workflow dialog with the JSON url https://www.example.com entered, and Cancel and Trigger Workflow buttons.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/manual-trigger-probe.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=331e628511aa75d86915c0c8f7279cab)

**2. Check one monitor**

Open Uptime - Check Monitor and trigger it with the first monitor's row:

```json theme={null}
{ "id": "1", "name": "Client portal", "url": "https://www.example.com", "status": "unknown", "failures": "0" }
```

The run reaches Finish Workflow without sending an email, and that monitor's `status` in your database is now `up`.

**3. Run a sweep from the app**

Open the live app link from the last build step and click Check now. The page refreshes 30 seconds later, and Uptime - Check Monitor's Runs has one run per monitor.

**4. Test an alert**

Add a monitor that can't answer, click **Check now**, wait for the page to refresh and click it again. The second failure marks Alert test down and emails DOWN: Alert test.

```sql theme={null}
INSERT INTO uptime_monitors (name, url) VALUES ('Alert test', 'https://down.example.invalid');
```

Then delete it: `DELETE FROM uptime_monitors WHERE name = 'Alert test';`

## Go live

Open Uptime - Dispatch Checks, click Trigger, then **Add Trigger**, set Trigger Type to Cron, enter `*/5 * * * *` and click Save.

![The Trigger panel with Trigger 1 expanded: Trigger Type Cron and Cron set to \*/5 \* \* \* \*, with the hint Cron expression for when to trigger the workflow (UTC), and an Add Trigger button.](https://mintcdn.com/triggr/_d7ojda9vf8IHE31/images/studio/internal-apps/dispatch-cron-trigger.png?fit=max\&auto=format\&n=_d7ojda9vf8IHE31\&q=85\&s=4ee8b5bb71af02d3427b0a2d614f60a5)

Check all four workflows show Active on Workflows (imports start Active). Add a row to `uptime_monitors` for each site to watch, and the next sweep picks it up.

## FAQ

> [accordion] Can it check more often than every five minutes?
>
> No. [Scheduled runs](https://kb.mspprocess.com/studio/workflows/triggers.md#cron) are at least five minutes apart.

> [accordion] Why are there so many runs?
>
> Each sweep is one Uptime - Dispatch Checks run plus a check and a probe per monitor (21 runs for 10 monitors), and the page adds an Uptime - Status run each time it loads.

> [accordion] How do I stop uptime\_checks growing forever?
>
> Add a workflow with a Cron trigger such as `15 3 * * *` and an Execute SQL step that runs `DELETE FROM uptime_checks WHERE checked_at < now() - interval '90 days'`.

> [accordion] Can it check sites on a client's internal network?
>
> No. Requests come from Triggr's servers, so each URL must be reachable from the internet; a site with an allow list needs [Triggr's IP addresses](https://kb.mspprocess.com/security/ip-allowlist.md).

> [accordion] Can I share the page with clients?
>
> Not as built: every client would see every monitor. Keep it Internal, or build a page per client with its own Status workflow.

## Troubleshooting

> [accordion] Test Connection fails, and my password has special characters
>
> Click **Edit** on the connection and type the password into Password; a connection string needs `@`, `:` and `/` encoded.

> [accordion] Execute SQL fails with “invalid SQL query”
>
> Triggr checks each query with `EXPLAIN` first, so statements such as `CREATE TABLE` fail. Create tables in your database's own SQL editor.

> [accordion] A run fails with “trigger node … not found” or “no trigger node found”
>
> The pasted JSON didn't start with a top-level `"id"`. Import the JSON from this page again.

> [accordion] The page shows “Status request failed (HTTP 404)” or Check now shows “this app is not allowed to run that workflow”
>
> Check the two IDs in the page, that Uptime - Status is saved with its Webhook trigger, and that Uptime - Dispatch Checks is ticked under Workflows pages can run.

> [accordion] Check now shows “triggr is not defined”
>
> You opened the page with Open. Use the live app link from the last build step.

> [accordion] A monitor stays unknown
>
> It hasn't been checked yet, or has failed once and not been up since; its `last_error` column says why.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.