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
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
webhookUrl | string | Yes | — | Public HTTPS endpoint to receive deliveries (see Endpoint Requirements) |
metricSets | string[] | Yes | — | One or more of: request_latency, index_stats, jobs |
cadence | string | No | 1h | How often the webhook fires: 5m, 15m, 30m, 1h |
period | string | No | 1h | Data point granularity: 5m, 15m, 30m, 1h |
timeRange | string | No | 12h | How far back each delivery covers: 1h, 6h, 12h, 1d, 3d, 7d |
indexAllowlist | string[] or null | No | null | Index 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"
}
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"
}
]
}
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": "..."}.
| Status | Body | Cause |
|---|---|---|
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. |
422 | Validation detail under error | A 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 Set | Description |
|---|---|
request_latency | API request duration (p50/p90/p99) and request counts by endpoint and HTTP status |
index_stats | Current document count and vector count per index |
jobs | Indexing 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.
| Value | Description |
|---|---|
5m | Every 5 minutes |
15m | Every 15 minutes |
30m | Every 30 minutes |
1h | Every hour (default) |
Period
The granularity of each data point in the payload. A shorter period produces more data points per delivery.
| Value | Description |
|---|---|
5m | One data point every 5 minutes |
15m | One data point every 15 minutes |
30m | One data point every 30 minutes |
1h | One data point every hour (default) |
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.
| Value | Description |
|---|---|
1h | Last 1 hour |
6h | Last 6 hours |
12h | Last 12 hours (default) |
1d | Last 1 day |
3d | Last 3 days |
7d | Last 7 days |
To keep payload sizes reasonable, the maximum timeRange depends on the period:
| Period | Max Time Range | Max Data Points |
|---|---|---|
5m | 1d | 288 |
15m | 3d | 288 |
30m | 7d | 336 |
1h | 7d | 168 |
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
| Header | Value |
|---|---|
Content-Type | application/json |
Content-Encoding | gzip |
X-Webhook-Signature | t=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, orconflict(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
| Metric | Metric Set | Type | Unit | Attributes |
|---|---|---|---|---|
marqo.request.duration | request_latency | summary | ms | operation |
marqo.request.count | request_latency | sum | 1 | endpoint, status_class |
marqo.index.doc_count | index_stats | gauge | 1 | — |
marqo.index.vector_count | index_stats | gauge | 1 | — |
marqo.jobs.total | jobs | sum | 1 | status |
marqo.jobs.documents_total | jobs | sum | 1 | result, status |
marqo.jobs.avg_duration | jobs | gauge | s | — |
marqo.jobs.avg_delay | jobs | gauge | s | — |
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.