CountWatchDocs

Docs / Connect your data

Base44

Count each call of your Base44 app's backend functions, ok or error, and get an alert when they spike, drop or stop.

In short: make a webhook URL in the dashboard (Sources) and add it as a Base44 secret, wrap each backend function with a small helper, then redeploy the functions and publish the app. Each call counts one, as ok or error.

The detailed steps below work for you or your coding agent. Your agent can do every step that needs no secret; anything secret, such as a webhook URL or a key, you make in the dashboard and paste yourself.

Counts each call of your app's backend functions as one hit to a webhook URL, with the function's group and the outcome (ok or error) as dimensions. Each group × outcome is one watch (a push watch: it updates at once; mind the plan's watch limit and group related functions).

Base44 traps first: editing a function file (in the sandbox, or with Base44's edit_file, write_file or run_command) does not deploy it. Deploy each function ("Save & Redeploy" in the editor, or base44 functions deploy), then publish the app again: the published app keeps the old functions until then. Secrets reach functions as Deno.env, not the sandbox shell.

1. The webhook URL

The person makes a webhook URL for the metric (for example app_actions) in the dashboard (Sources: https://magnum-staging.elirais.workers.dev/dashboard/sources) and adds it as the Base44 secret COUNTWATCH_WEBHOOK_URL. It is a secret: keep it out of chat (link previews are ignored, but anyone who has the link can send counts).

2. The wrapper

Save as functions/shared/countwatch.ts (or next to each function):

// countwatch.ts — CountWatch for Base44 backend functions (Deno). Wrap a function's handler and each call
// sends one hit to COUNTWATCH_WEBHOOK_URL with ?group=<group>&outcome=ok|error: ok on 2xx, error on 5xx or a thrown
// error; a 4xx (the caller's mistake) is not counted. It never changes the response, waits at most 1.5 s, and does
// nothing when the secret is not set.
type Handler = (req: Request) => Response | Promise<Response>;

async function hit(group: string, outcome: "ok" | "error"): Promise<void> {
  const link = Deno.env.get("COUNTWATCH_WEBHOOK_URL");
  if (!link) return;
  const url = new URL(link);
  url.searchParams.set("group", group);
  url.searchParams.set("outcome", outcome);
  try {
    await fetch(url, { method: "POST", signal: AbortSignal.timeout(1500) });
  } catch {
    // counting never breaks the function
  }
}

export function withCountWatch(group: string, handler: Handler): Handler {
  return async (req) => {
    let res: Response;
    try {
      res = await handler(req);
    } catch (err) {
      await hit(group, "error");
      throw err;
    }
    if (res.status >= 200 && res.status < 300) await hit(group, "ok");
    else if (res.status >= 500) await hit(group, "error");
    return res;
  };
}

Wrap each function's handler:

import { withCountWatch } from "../shared/countwatch.ts";

Deno.serve(withCountWatch("finalize", async (req) => {
  // … the function as before
  return Response.json({ ok: true });
}));

Do not wrap the function that receives CountWatch alerts: its calls would feed back into the counts.

3. Deploy and test

Redeploy every wrapped function, then publish the app. Call one function without signing in (a function that calls base44.auth.me() answers 500): the outcome=error watch for its group appears in check_setup. A normal call adds to outcome=ok.

4. Alerts to a function

A Base44 webhook trigger (https://cwebhooks.base44.com/api/hooks/…) queues the request and answers before your code runs, so it cannot answer the challenge. Use the receiving function's own URL, https://<app>.base44.app/functions/<name>, and answer the challenge in it:

Deno.serve(async (req) => {
  const raw = await req.text(); // the signature covers these exact bytes
  const body = JSON.parse(raw);
  if (body.type === "destination.verify") return Response.json({ challenge: body.data.challenge });
  // … an alert: verify raw against the webhook-signature header with COUNTWATCH_WEBHOOK_SECRET (Standard Webhooks),
  // then act on body
  return new Response(null, { status: 204 });
});

Then manage {"action": "create_destination", "body": {"url": "https://<app>.base44.app/functions/<name>"}} and verify_destination.

Check it works

  1. Data arrives. check_setup (or curl -s https://magnum-staging.elirais.workers.dev/v1/status -H "Authorization: Bearer $COUNTWATCH_API_KEY"): the app_actions watch shows last_request_at, and missing_buckets_24h stays 0 (explicit watches). Do what next_actions says.
  2. History is in. GET /v1/imports/{import_id} shows status: done and the rules chosen for app_actions, with the alerts they would have raised. (Skipped the import? The watch learns for about 22 days; starter rules apply meanwhile.)
  3. An alert reaches you. The person turns on e-mail alerts in the dashboard (https://magnum-staging.elirais.workers.dev/dashboard), or you add a webhook (manage create_destination, then verify_destination; the endpoint answers the challenge, see https://magnum-staging.elirais.workers.dev/agent). A write key cannot read alerts: read them with MCP alerts, or with GET /v1/alerts and an admin key. Then manage {"action": "send_test_alert"}: every active destination receives a test alert (test: true) within a few minutes.

For agents: this page as Markdown.