theuxbites webhooks

theuxbites can send a signed JSON event to an address you choose when something happens in your account: an audit finishes, the daily watch sees something worse, a site audit or a prospect list is done, or an audit speaks to an issue you marked fixed. The address can be your own server, a Zapier “Catch Hook” or a Make “Custom webhook”, so the events can reach Slack, a spreadsheet, a ticket or a CRM without code.

Part of the Studio plan. See plans.

Set it up

  1. In theuxbites, open the user menu and choose Webhooks.
  2. Paste the address that should receive the events. It must start with https://, use the standard port and resolve to a public internet address. Up to 5 addresses per account.
  3. Tick the events it should receive.
  4. Choose “Send test event”. A webhook.test event is sent at once, signed like any other, and the page says what your endpoint answered.
  5. On your own server, check the signature (below) with the secret shown under “Show secret”.

Each endpoint has a switch to stop it, a delivery log of its last 30 events with what was sent and what came back, and “Resend” for any of them.

What is sent

A POST with a JSON body. Every event has the same envelope: id (also in the webhook-id header), type, created_at and data.

{
  "id": "evt_3f9c2b7a51d84e0c9a6b1d2e4f5a6b7c",
  "type": "audit.completed",
  "created_at": "2026-10-24T09:12:31.000Z",
  "data": {
    "report_id": "6b1f…",
    "project_id": "0c42…",
    "url": "https://example.com/pricing",
    "completed_at": "2026-10-24T09:12:30.512Z",
    "ux_score": 78,
    "scores": { "performance": 64, "accessibility": 92, "seo": 100, "ai_visibility": 85 },
    "findings": { "total": 14, "critical": 1, "quick_wins": 6, "planned": 7 },
    "report_url": "https://theuxbites.com/projects/0c42…"
  }
}

The events say what an audit or a check observed, in the same words as the report and the emails. No AI writes any of it. Scores are 0 to 100; a part the run could not measure is null, not 0. Fields may be added over time; a receiver should ignore what it does not know.

Events

  • Audit completed audit.completed

    An audit of a page finished, whoever started it: you, a schedule, your assistant, a bulk list, a site audit or a competitor comparison.

    report_id
    The audit.
    project_id
    The project the page belongs to.
    url
    The audited page.
    completed_at
    When the audit finished (ISO 8601).
    ux_score
    The UX score, 0 to 100, the same number the report shows.
    scores
    performance, accessibility, seo and ai_visibility, 0 to 100, or null when the run could not measure that part.
    findings
    total, critical, quick_wins and planned: the counts the report shows.
    report_url
    The project in theuxbites (sign-in needed).
  • Audit did not complete audit.failed

    An audit stopped without a result. It is not counted against your monthly allowance.

    report_id
    The audit.
    project_id
    The project, or null when the audit was not linked to one yet.
    url
    The page.
    failed_at
    When it was recorded (ISO 8601).
  • Daily watch found something worse watch.alert

    The daily watch of a project saw at least one change for the worse: a page that stopped loading, got noindex, lost its title, or a robots.txt that started blocking AI search crawlers. Sent once per check, like the email.

    project_id
    The watched project.
    checked_at
    When the check ran (ISO 8601).
    changes
    Every change of this check, worse first: url, kind, direction ("worse" or "better"), title and detail, the same sentences as the email.
    project_url
    The project in theuxbites.
  • Site audit finished site_audit.completed

    Every page of a site audit has finished, one way or the other.

    site_audit_id
    The site audit.
    project_id
    Its project.
    started_at
    When it was started (ISO 8601).
    pages
    total, completed, failed and skipped (not audited because the allowance ran out).
    page_list
    Each page: url, status, report_id and reused (an audit from the last day was used at no cost).
    site_audit_url
    The Site audit tab in theuxbites.
  • Prospect list finished prospect_batch.completed

    Every address of a bulk prospect list has finished, one way or the other.

    batch_id
    The list.
    created_at
    When it was started (ISO 8601).
    pages
    total, completed, failed and skipped.
    items
    Each address: url, status, project_id and report_id.
    list_url
    The list in theuxbites, with the extracts.
  • An audit spoke to an issue marked fixed fix.checked

    The first audit after an issue was marked fixed that measured that part of the page, and any later audit whose answer differs from the one before (found again, or no longer found).

    project_id
    The project.
    page_url
    The page the issue was marked on.
    issue_id
    The finding, as list_issues names it over MCP.
    title
    The finding’s title.
    marked_fixed_at
    When it was marked fixed (ISO 8601).
    audit_at
    When the audit ran (ISO 8601).
    report_id
    The audit.
    observation
    "not_found": the audit measured this part of the page and did not report the issue. "found": it reported it.
    note
    A sentence saying that one audit is an observation, not proof of a fix.
    fix_log_url
    The Fix log tab in theuxbites.

About fix.checked: One audit is an observation of the page on that day, not proof that a fix worked or did not. It follows the same rule as the Fix log tab, so the two never disagree.

Check the signature

Events are signed the Standard Webhooks way, so its libraries (for example standardwebhooks on npm and PyPI) and Svix’s can check them. Three headers come with every attempt:

  • webhook-id: the event’s id, the same on every retry, so a repeat can be dropped.
  • webhook-timestamp: seconds since 1970 when this attempt was sent.
  • webhook-signature: v1, followed by the base64 HMAC-SHA256 of id.timestamp.body, keyed with the base64-decoded part of the secret after whsec_. There may be more than one, separated by spaces; one match is enough.
import { createHmac, timingSafeEqual } from 'node:crypto';

// secret: "whsec_…" from the Webhooks page. body: the raw request body, unparsed.
export function verify(secret, headers, body) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signatures = headers['webhook-signature'] ?? '';
  // Refuse old or future events (replays): five minutes either way.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
  const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest('base64');
  return signatures.split(' ').some(entry => {
    const given = entry.split(',')[1] ?? '';
    return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  });
}

Sign the body exactly as it arrived: parsing and re-serialising the JSON changes the bytes. “Roll secret” makes a new secret, and the old one stops working at once.

Answering, retries and switching off

Answer with any 2xx status as soon as the event is stored; do slow work afterwards. A redirect is not followed and counts as not received, so give the final address. If your endpoint does not answer with a 2xx status within 10 seconds, the event is sent again after about 30 seconds and 2 minutes, then once a day for three days. Every attempt carries the same webhook-id, so a repeat can be recognised and dropped.

After 5 events in a row that could not be delivered after every retry, the endpoint is switched off and the settings page says why. Switch it on again when it is fixed.

The order of events is not guaranteed; use created_at and the ids in data. If the account leaves the Studio plan, the endpoints are kept and nothing is sent until the plan includes webhooks again.

Your data

theuxbites keeps each endpoint’s address, name and events, and a log of what it sent there: the event, the status your server answered and when. Signing secrets are not stored; they are derived on the server when an event is signed. What your endpoint does with an event is up to you and the service it belongs to. See the privacy policy.

Questions: hello@theuxbites.com