Webhooks

AquaMeter AI can push real-time event notifications to your server. Configure endpoints from the Webhooks page in your dashboard.

How webhooks work

1

Register an endpoint

Add your server URL and choose which events to subscribe to from the dashboard.

2

Event fires

When a reading completes, a usage threshold is hit, or a subscription changes, AquaMeter creates a delivery.

3

POST request sent

AquaMeter sends a signed POST request with a JSON payload to your endpoint.

4

You respond 200

Return HTTP 200 within 15 seconds to acknowledge receipt. Other status codes (including 201/202/204) are treated as failures and trigger a retry.

5

Retries on failure

Non-200 responses or timeouts are retried up to 3 times with delays of 1 min, 5 min, and 15 min.

Events

EventDescription
reading.completedA meter image was successfully read.
reading.failedA reading attempt failed or confidence was too low.
usage.threshold_reachedMonthly usage has reached 80% of the plan limit.
usage.limit_reachedMonthly request limit has been reached.
subscription.createdA new subscription was started.
subscription.cancelledA subscription was cancelled.
subscription.expiredA subscription has expired.

Payload structure

All webhook deliveries share the same envelope structure. The data field contains event-specific information.

JSON — reading.completed
{
"event": "reading.completed",
"timestamp": "2026-04-28T14:32:00.000Z",
"data": {
"reference_id": "9c7d3f2a-1e4b-4d5a-9b6c-1f2e3d4c5b6a",
"reading": "02390.276",
"confidence": 97,
"unobstructed": true,
"escalated": false,
"reading_id": 142
}
}

reference_id is always present and matches the value returned in the API ack — use it to correlate the delivery with the request.reading_id is the persisted database row id and is present only when a reading was successfully extracted (omitted on reading.failed).

JSON — usage.threshold_reached
{
"event": "usage.threshold_reached",
"timestamp": "2026-04-28T09:00:00.000Z",
"data": {
"user_id": 5,
"used": 200,
"limit": 250
}
}

Delivery headers

HeaderValue
Content-Typeapplication/json
X-AquaMeter-EventThe event name, e.g. reading.completed
X-AquaMeter-Signaturesha256=<HMAC-SHA256 signature of the body>

Verifying signatures

Each delivery is signed with your webhook's secret using HMAC-SHA256. Always verify the signature before processing the payload to ensure the request came from AquaMeter AI. Use a timing-safe comparison to prevent timing attacks.

Node.js
import crypto from "crypto";

function verifySignature(rawBody: string, secret: string, header: string): boolean {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(header),
  );
}

Retry behaviour

AttemptDelayTrigger
1st retry1 minuteNon-200 response or timeout on original attempt
2nd retry5 minutesNon-200 response or timeout on 1st retry
3rd retry15 minutesNon-200 response or timeout on 2nd retry
FailedMarked as failed after 3rd retry fails, or after the delivery sits in pending past the 1-hour stale window

Your endpoint must respond with HTTP 200 within 15 seconds. Process heavy work asynchronously — acknowledge receipt immediately and handle the payload in a background job. Note that 201/202/204 are currently treated as failures.