Skip to main content

Metrics Webhook

Push your Marqo index metrics to any HTTPS endpoint on a configurable schedule. Each delivery is an OTLP JSON payload signed with HMAC-SHA256, so you can verify authenticity and feed the data directly into any OpenTelemetry-compatible observability system.

Base URL​

https://ecom.marqo-ep.ai/api/v1/webhooks/metrics

Authentication​

Include your API key in the Authorization header of every request using the Bearer scheme:

Authorization: Bearer {api_key}

Create a Webhook​

Register a new HTTPS endpoint to receive metrics pushes.

POST /api/v1/webhooks/metrics

Request Body​

ParameterTypeRequiredDefaultDescription
webhookUrlstringYes—Public HTTPS endpoint to receive deliveries (see Endpoint Requirements)
metricSetsstring[]Yes—One or more of: request_latency, index_stats, jobs
cadencestringNo1hHow often the webhook fires: 5m, 15m, 30m, 1h
periodstringNo1hData point granularity: 5m, 15m, 30m, 1h
timeRangestringNo12hHow far back each delivery covers: 1h, 6h, 12h, 1d, 3d, 7d
indexAllowliststring[] or nullNonullIndex names to include. null means all indexes

Example Request​

curl -X POST "https://ecom.marqo-ep.ai/api/v1/webhooks/metrics" \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-endpoint.example.com/marqo-metrics",
"metricSets": ["request_latency", "index_stats", "jobs"],
"cadence": "1h",
"period": "1h",
"timeRange": "12h"
}'

Example Response (201)​

{
"webhookId": "b69c30b4-198a-4dae-ab7a-6e75304e57b8",
"webhookUrl": "https://your-endpoint.example.com/marqo-metrics",
"metricSets": ["request_latency", "index_stats", "jobs"],
"cadence": "1h",
"period": "1h",
"timeRange": "12h",
"indexAllowlist": null,
"signingSecret": "whsec_Xge2WnZ_ebTmHfAbZ9S5vNRGsOH...",
"enabled": true,
"createdAt": "2026-04-22T04:29:51.435748+00:00"
}
Save Your Signing Secret

The signingSecret is only returned at creation time. Store it securely — you will need it to verify webhook signatures. It cannot be retrieved later.

List Webhooks​

Retrieve all webhook configurations for your account.

GET /api/v1/webhooks/metrics

Example Response (200)​

{
"webhooks": [
{
"webhookId": "b69c30b4-198a-4dae-ab7a-6e75304e57b8",
"webhookUrl": "https://your-endpoint.example.com/marqo-metrics",
"metricSets": ["request_latency", "index_stats", "jobs"],
"cadence": "1h",
"period": "1h",
"timeRange": "12h",
"indexAllowlist": null,
"enabled": true,
"createdAt": "2026-04-22T04:29:51.435748+00:00"
}
]
}
note

The signingSecret is not included in list responses.

Delete a Webhook​

Remove a webhook configuration. No further deliveries are scheduled; a delivery that was already being sent when you deleted the webhook can still arrive within a few seconds.

DELETE /api/v1/webhooks/metrics/{webhookId}

Response: 204 No Content

Deleting a webhook ID that does not exist returns 404.


Errors​

Errors return {"error": "..."}.

StatusBodyCause
400{"error": "Webhook URL must use HTTPS"}webhookUrl does not use the https scheme.
400{"error": "Webhook URL must have a valid hostname"}webhookUrl has no hostname.
400{"error": "At least one metric set must be specified"}metricSets is an empty array.
400{"error": "time_range '3d' exceeds maximum '1d' for period '5m'"}timeRange and period are both valid on their own but the combination exceeds the maximum in Time Range.
401{"error": "Unauthorized"}Missing or invalid API key.
404{"error": "Webhook 'wh_abc123' not found"}Deleting a webhook ID that does not exist, or one belonging to another account.
422Validation detail under errorA value outside the accepted set for metricSets, cadence, period or timeRange, or a malformed body. The accepted values are listed under Configuration.

Note that the time_range and period field names appear in their internal snake_case form in the message text, while the request body uses timeRange and period.


Configuration​

Metric Sets​

Choose which categories of metrics to receive. You can select one or more per webhook:

Metric SetDescription
request_latencyAPI request duration (p50/p90/p99) and request counts by endpoint and HTTP status
index_statsCurrent document count and vector count per index
jobsIndexing job counts, document processing counts, average job duration and delay

Cadence​

How often the webhook fires. Each cadence interval triggers a delivery to your endpoint.

ValueDescription
5mEvery 5 minutes
15mEvery 15 minutes
30mEvery 30 minutes
1hEvery hour (default)

Period​

The granularity of each data point in the payload. A shorter period produces more data points per delivery.

ValueDescription
5mOne data point every 5 minutes
15mOne data point every 15 minutes
30mOne data point every 30 minutes
1hOne data point every hour (default)
note

Request metrics (request_latency) are sampled every 5 minutes. With period: "5m", each request_latency data point is computed over the trailing 15 minutes, so latency percentiles are smoothed and request counts are the 15-minute average rate scaled to 5 minutes. Use a period of 15m or longer if you need each data point to reflect only its own window.

Time Range​

How far back each delivery covers. The number of data points per delivery is timeRange ÷ period.

ValueDescription
1hLast 1 hour
6hLast 6 hours
12hLast 12 hours (default)
1dLast 1 day
3dLast 3 days
7dLast 7 days

To keep payload sizes reasonable, the maximum timeRange depends on the period:

PeriodMax Time RangeMax Data Points
5m1d288
15m3d288
30m7d336
1h7d168

Index Allowlist​

By default (null), metrics for all indexes in your account are delivered. Each index produces a separate delivery. To limit to specific indexes:

{
"indexAllowlist": ["my-products", "my-articles"]
}

Configuration Examples​

High-Frequency Monitoring​

Monitor a single production index every 5 minutes with fine-grained data points:

curl -X POST "https://ecom.marqo-ep.ai/api/v1/webhooks/metrics" \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-endpoint.example.com/marqo-metrics",
"metricSets": ["request_latency", "index_stats"],
"cadence": "5m",
"period": "5m",
"timeRange": "1h",
"indexAllowlist": ["production-index"]
}'

This delivers 12 data points (1h ÷ 5m) every 5 minutes for one index.

Hourly Summary​

Get a broad view of all indexes with hourly granularity:

curl -X POST "https://ecom.marqo-ep.ai/api/v1/webhooks/metrics" \
-H "Authorization: Bearer {api_key}" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://your-endpoint.example.com/marqo-metrics",
"metricSets": ["request_latency", "index_stats", "jobs"],
"cadence": "1h",
"period": "1h",
"timeRange": "1d"
}'

This delivers 24 data points (1d ÷ 1h) every hour for all indexes.


Payload Format​

Each delivery is an OTLP JSON payload. One delivery is sent per index, per webhook, per cadence interval.

Structure​

{
"resourceMetrics": [
{
"resource": {
"attributes": [
{ "key": "service.name", "value": { "stringValue": "marqo" } },
{ "key": "marqo.index.name", "value": { "stringValue": "my-index" } }
]
},
"scopeMetrics": [
{
"scope": {
"name": "marqo.metrics.webhook",
"version": "1.0.0"
},
"metrics": [...]
}
]
}
]
}

Delivery Headers​

HeaderValue
Content-Typeapplication/json
Content-Encodinggzip
X-Webhook-Signaturet=1713758400,v1=a1b2c3d4...

The body is gzip-compressed. Decompress it before parsing.


Metrics Reference​

request_latency​

marqo.request.duration — Summary (ms)​

Request latency percentiles grouped by API operation. Each data point includes p50, p90, and p99 values.

{
"name": "marqo.request.duration",
"unit": "ms",
"summary": {
"dataPoints": [
{
"startTimeUnixNano": "1713754800000000000",
"timeUnixNano": "1713758400000000000",
"quantileValues": [
{ "quantile": 0.5, "value": 47.80 },
{ "quantile": 0.9, "value": 125.30 },
{ "quantile": 0.99, "value": 450.00 }
],
"attributes": [
{ "key": "operation", "value": { "stringValue": "POST /api/v1/indexes/:index/search" } }
]
}
]
}
}

marqo.request.count — Sum (delta)​

Number of requests in each period, grouped by endpoint and status_class (2XX, 4XX, 5XX).

Counts are derived from metrics sampled every 5 minutes, so they are close approximations rather than exact tallies: requests made near a window boundary can be counted in the neighbouring window, and the very first request for a new endpoint/status_class combination may not be counted. For exact per-request outcomes, use your own API logs.

index_stats​

marqo.index.doc_count — Gauge​

Current number of documents in the index. Single data point per delivery.

marqo.index.vector_count — Gauge​

Current number of vectors in the index. Single data point per delivery.

jobs​

marqo.jobs.total — Sum (delta)​

Number of indexing jobs that finished in each period, grouped by the job's final status: COMPLETED, ERROR, FAILED, CONFLICT or CANCELLED. See Job Statuses for what each status means. A job reports a single status even when it contains a mix of outcomes, so ERROR and FAILED jobs usually still processed most of their documents.

marqo.jobs.documents_total — Sum (delta)​

Number of documents in jobs that finished in each period, grouped by:

  • result: the outcome for those documents: processed, error (rejected for a reason on your side, such as a field with the wrong type), failed (server-side failure; resend later), skipped, or conflict (a newer version was already indexed)
  • status: the final status of the job the documents belonged to

Because status is per job, a single job can contribute result: "processed" and result: "error" data points that both carry status: "ERROR".

marqo.jobs.avg_duration — Gauge (seconds)​

Average job processing time per period window.

marqo.jobs.avg_delay — Gauge (seconds)​

Average time between job creation and processing start per period window.

Summary Table​

MetricMetric SetTypeUnitAttributes
marqo.request.durationrequest_latencysummarymsoperation
marqo.request.countrequest_latencysum1endpoint, status_class
marqo.index.doc_countindex_statsgauge1—
marqo.index.vector_countindex_statsgauge1—
marqo.jobs.totaljobssum1status
marqo.jobs.documents_totaljobssum1result, status
marqo.jobs.avg_durationjobsgauges—
marqo.jobs.avg_delayjobsgauges—

All sums use delta aggregation temporality (aggregationTemporality: 1): each data point is the count for its own window between startTimeUnixNano and timeUnixNano, not a running total. Configure your OpenTelemetry collector or backend for delta sums, or add the points up yourself.

index_stats and jobs data points are only included when data exists for the window. An index with no indexing activity in the delivery's time range has no jobs metrics, and an index without document statistics has no index_stats metrics.


Signature Verification​

Every delivery includes an X-Webhook-Signature header for authenticity verification. The signature is computed as HMAC-SHA256(signing_secret, "{timestamp}.{uncompressed_body}").

Python​

import gzip
import hashlib
import hmac
import json


def verify_webhook(raw_body: bytes, signature_header: str, signing_secret: str) -> dict:
"""Verify and parse a Marqo metrics webhook delivery."""
# Decompress
body = gzip.decompress(raw_body)

# Parse header: "t=TIMESTAMP,v1=SIGNATURE"
parts = dict(p.split("=", 1) for p in signature_header.split(","))
timestamp = parts["t"]
signature = parts["v1"]

# Compute expected signature
message = f"{timestamp}.".encode("utf-8") + body
expected = hmac.new(
signing_secret.encode("utf-8"), message, hashlib.sha256
).hexdigest()

if not hmac.compare_digest(signature, expected):
raise ValueError("Invalid webhook signature")

return json.loads(body)

Node.js​

const crypto = require("crypto");
const zlib = require("zlib");

function verifyWebhook(rawBody, signatureHeader, signingSecret) {
const body = zlib.gunzipSync(rawBody);

const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=", 2))
);

const expected = crypto
.createHmac("sha256", signingSecret)
.update(`${parts.t}.`)
.update(body)
.digest("hex");

if (parts.v1 !== expected) {
throw new Error("Invalid webhook signature");
}

return JSON.parse(body);
}

Endpoint Requirements​

Your webhook endpoint must:

  • Accept HTTPS POST requests
  • Resolve to a public IP address. URLs whose host is localhost, a private, loopback or link-local address, or a name that resolves to one, are rejected when you create the webhook or blocked at delivery time
  • Return a 2xx status code within 10 seconds. Redirects (3xx) are not followed and count as a failed delivery, so register the final URL
  • Handle gzip-compressed request bodies

If your endpoint is unreachable or returns a non-2xx status, the delivery is retried automatically. After repeated failures, the delivery is dropped.


FAQ​

How many webhooks can I create?

You can create multiple webhooks with different configurations — different metric sets, cadences, or index filters.

Can I use different cadences for different metric sets?

Yes. Create separate webhooks, each with its own cadence and metric set selection.

What happens if my endpoint is down?

Deliveries are retried automatically. If your endpoint remains unreachable after all retries, the delivery is dropped.

What happens when I create a new index?

Metrics appear in webhook deliveries after the index has been active long enough for data to be collected (typically a few minutes).

Are deliveries per-index or aggregated?

Per-index. If you have 3 indexes and no indexAllowlist, you receive 3 separate deliveries per cadence interval.

Can I recover my signing secret?

No. The signing secret is shown only at creation time. If you lose it, delete the webhook and create a new one.

What do startTimeUnixNano and timeUnixNano mean?

These define the time window for each data point. The difference between them equals your configured period. For example, with period: "1h", each data point covers a 1-hour window.