Webhooks
Sends signed webhooks to customer endpoints with retries and delivery logs, and verifies incoming ones. $1.00 per 100K messages + $5.00 per 100K deliveries. The base URL is https://www.motor.dev/webhooks, and the OpenAPI 3.1 specification describes each endpoint.
Sign in to try the API here with one of your team's keys. Requests use that key's permissions and spending limit. Standard charges apply.
Quick start
Webhooks sends signed events from your application to your customers' servers, with retries, history, and replay. You manage everything from your backend with a secret key (motor_sk_...). Every POST needs an Idempotency-Key: send a new value for each operation and the same value when you retry it.
1. Create a tenant for one of your customers:
curl https://api.motor.dev/webhooks/tenants \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"external_id": "customer-42", "name": "Acme Corp"}'
2. Add an event type to your catalog. Motor refuses messages of types that the catalog doesn't have:
curl https://api.motor.dev/webhooks/event_types \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name": "invoice.paid", "description": "An invoice was paid."}'
3. Add your customer's endpoint. The response includes signing_secret (whsec_...) once. Give it to your customer so they can verify deliveries:
curl https://api.motor.dev/webhooks/endpoints \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"tenant_id": "wdt_...", "url": "https://hooks.acme.example/motor", "events": ["invoice.*"]}'
4. Send a test message to the endpoint's real receiver. It's a paid motor.test message that skips filters and transforms:
curl https://api.motor.dev/webhooks/endpoints/wde_.../test \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"payload": {"test": true}}'
5. Publish real events:
curl https://api.motor.dev/webhooks/messages \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-paid-inv_42" \
-d '{"tenant_id": "wdt_...", "event_type": "invoice.paid", "payload": {"invoice_id": "inv_42", "amount": "49.00"}}'
Motor answers 202 once the message, its deliveries, and its charge are stored. It doesn't wait for the receiver. Follow progress with GET /messages/wdm_... and GET /deliveries?message_id=wdm_....
Webhooks is live-only: there's no test mode. Omit Motor-Mode or send Motor-Mode: live. To try things out, point a separate tenant at your own staging receiver.
Publishing messages
Each message has one tenant, one event type, and immutable bytes. Send JSON as payload, or other content as body_base64 with a content_type (application/json, text/plain, application/octet-stream, or application/x-www-form-urlencoded). Bodies can be up to 1 MiB.
Motor delivers your payload's exact bytes. It removes whitespace outside strings but never rewrites the JSON: key order, escapes, and number spellings such as 100, 1.50, and 1e2 reach receivers as you wrote them, and signatures cover those bytes.
By default a message goes to every enabled or paused endpoint of its tenant whose events and filter match. endpoint_ids narrows that to a list of up to 100 endpoints. A message reaches at most 100 endpoints, with at most 8 MiB of transformed bodies in all. Motor freezes the endpoints and each delivery's bytes when it accepts the message: later endpoints don't receive it, and later changes to filters or transforms don't rewrite it.
Everything about how to send (the URL, custom headers, authorization, signing keys, timeout, and retry delays) comes from the endpoint each time Motor sends. Fixing an endpoint's URL moves its scheduled and retrying deliveries to the new URL.
deliver_at schedules a message up to 7 days ahead. Deliveries stop at expires_at, which defaults to 72 hours after deliver_at. A retry with the same Idempotency-Key and the same meaning, even with different whitespace, key order, or number spelling, returns the first response and keeps the first bytes. external_id is for your own lookups; it doesn't deduplicate.
Message statuses come from their first deliveries: scheduled, queued, delivering, succeeded, partially_failed, failed, canceled, or no_destinations. Replays have their own statuses and never change these.
Verifying deliveries
Motor signs every delivery with the Standard Webhooks scheme, so receivers can use its maintained libraries. Each request has:
webhook-id: the message ID. It's the same on retries and replays: deduplicate on it.webhook-timestamp: Unix seconds of this attempt.webhook-signature: v1, and the Base64 HMAC-SHA256 of the ID, ., the timestamp, ., and the exact body, with the key in the endpoint's signing_secret after whsec_. During a key rotation there are two signatures, separated by a space.Motor-Delivery-Id, Motor-Attempt-Id, and Motor-Event-Type: for diagnostics. The signature doesn't cover them, so put the event type in your payload if receivers must trust it.
from standardwebhooks import Webhook
wh = Webhook("whsec_...")
event = wh.verify(request.body, request.headers) # raises if invalid
Receivers should compare signatures in constant time, reject timestamps more than 5 minutes from now, and record the webhook-id in the same transaction as their side effects. Deliveries can arrive more than once and out of order. Any 2xx response counts as received.
Motor does not support static sender IP addresses. Sender addresses may change, so receivers requiring source-IP allowlisting are unsupported. Verify every delivery's signature.egress_ipv4_cidrs in GET /capabilities is empty. Deliveries use IPv4 only, so an endpoint's host needs an IPv4 address.
Rotate a key with POST /endpoints/wde_.../rotate_secret and If-Match. The old key keeps signing for overlap_seconds (a day by default); 0 retires it at once.
Filters and transforms
An endpoint's events lists the types it wants: exact names, invoice.* for everything under invoice., or *. A filter then tests the JSON body with conditions on JSON Pointers:
"filter": {
"all": [
{"path": "/amount", "op": "gte", "value": 1000},
{"path": "/currency", "op": "in", "value": ["usd", "eur"]}
]
}
Combine conditions with all, any, and not, up to 5 levels and 50 conditions. Operators are eq, ne, in, exists, gt, gte, lt, lte, and prefix. Numbers compare exactly, so 1.0e3 equals 1000. A missing value differs from null: only exists: false matches it.
A transform builds a new JSON object from pointers and literals:
"transform": {
"fields": [
{"name": "invoice", "from": "/invoice_id", "required": true},
{"name": "source", "value": "billing"}
]
}
Fields are sorted by name, and copied values keep their exact bytes. If a transform fails, such as for a missing required field, Motor refuses the whole message with 422 transform_failed. To check a filter and transform without sending or paying, call POST /previews with an endpoint_id and a payload.
Retries, replay, and recovery
Motor retries connection errors, timeouts, 408, 425, 429, and 5xx. The default delays are 15 seconds, 1 minute, 5 minutes, 15 minutes, 1 hour, 4 hours, 12 hours, and 1 day, each with up to 20% added, for nine attempts at most. A longer Retry-After on a 429 or 503 wins. Redirects and other 4xx responses fail at once. Automatic retries are included in the delivery price.
A 410, or a destination that resolves to a private address, disables the endpoint and cancels its waiting deliveries. After ten failures in a row, the endpoint's circuit opens for a minute; then one delivery probes it, and each failed probe doubles the wait up to an hour. Fix the receiver, then call POST /endpoints/wde_.../resume.
POST /deliveries/wdd_.../retry_now makes a delivery that waits to retry due now. It uses its existing attempts and costs nothing.POST /deliveries/wdd_.../replay sends a finished delivery again as a new paid generation, with the same webhook-id. "configuration": "current" applies the endpoint's current transform.POST /replays replays an endpoint's failed and expired deliveries in a window of up to 30 days. Each batch is charged as it's created; the job stops at the key's spending limit or at max_cost_usd.POST /messages/wdm_.../cancel stops deliveries that haven't started. POST /messages/wdm_.../redact erases a message's content for good and cancels all of its deliveries.
Failed and expired deliveries are your dead letters: list them with GET /deliveries?status=failed. Poll GET /events for failures, disabled endpoints, and finished replays.
Receiving other providers' webhooks
A source gives another provider a receive URL. Motor verifies each request, deduplicates it, and turns it into a message of the source's tenant, which then fans out like any other. Create one with the provider's secret:
curl https://api.motor.dev/webhooks/sources \
-H "Authorization: Bearer $MOTOR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"tenant_id": "wdt_...", "name": "Stripe live", "verification": {"kind": "stripe", "secret": "whsec_..."}, "event_type": "stripe.event", "event_type_path": "/type", "event_type_prefix": "stripe."}'
The response includes receive_url once. Verifiers are standard_webhooks, stripe, github, shopify, slack, twilio, bearer, and hmac, a template for other signed providers. Motor checks the original bytes and the URL as called, and refuses a request whose security header appears twice.
Requests with the same provider event ID, or for most verifiers the same body within 24 hours, are duplicates: Motor answers 200 with the first message's ID and doesn't charge again. Slack gets an empty 200 (and its URL verification challenge), and Twilio gets <Response/>. Motor answers only after the message and its charge are stored, so a provider that sees an error retries safely.
Every mapped event type must be in your catalog. Captures are charged to the source's billing_key_id, the key that created it by default. Rotate the URL with rotate_token and the secret with rotate_secret, each with an overlap of up to a day.
Pricing
Every accepted message is paid. There's no free allowance.
| Meter | Price | Charged for |
|---|
webhooks.ingest_64k | $1.00 / 100,000 | Each 64 KiB of a message, at least one unit, even with no destinations |
webhooks.delivery_16k | $5.00 / 100,000 | Each 16 KiB of each endpoint's delivery, at least one unit |
| Example | Charge |
|---|
| 1,000,000 one-KiB messages, one endpoint each | $60 |
| The same messages, three endpoints each | $160 |
| One 100-KiB message, two endpoints | $0.00072 |
| Replay one of those deliveries | $0.00035 |
| One one-KiB message that matches no endpoint | $0.00001 |
The price of a message, with its units and rate version, is in its price. Automatic retries are included. Replays and endpoint tests are charged like new deliveries. Failed, canceled, and expired deliveries aren't refunded. Charges count toward each key's monthly spending limit; work that would exceed it gets 402 spending_limit. See daily charges in Usage.
Limits and retention
| Limit | Default |
|---|
| Message admissions and source captures | 100 a second |
| Configuration writes | 20 a second |
| Reads | 100 a second |
| Tenants / event types / endpoints / sources | 10,000 / 1,000 / 10,000 / 1,000 |
| Endpoints of a tenant | 100 |
| Deliveries waiting to be sent | 100,000 |
| Stored message content | 10 GiB |
| Requests in flight to all your endpoints | 3 |
| Requests in flight for all teams | 6 |
GET /capabilities shows your effective limits. To raise one, contact support@motor.dev.
Delivery capacity is small for now. Motor sends at most 3 requests of your team at once, and 6 for all teams together, and these can't be raised yet. Each request holds its place until the receiver answers, so throughput depends on how fast your receivers are: at the 30-second timeout, 3 requests are about 0.1 attempts a second. The dispatch_rate of 1,000 attempts a second and the admission_rate of 100 messages a second are ceilings that Motor hasn't yet shown under load, so GET /capabilities marks them "measured": false. A team can also store at most 4 messages at once: publish with at most 4 requests in flight, and retry a 429 after its Retry-After.
Message content and attempt history stay 30 days by default; set payload_retention_days (1 to 30) with PATCH /settings. Content stays at least until its deliveries expire. Receiver response bodies are kept only if you turn on response_body_logging, for up to 7 days. Operational events stay 30 days, and idempotency keys and source event IDs 35 days.
Migrating from Svix, Hookdeck, or Convoy
Motor doesn't import from other providers or impersonate their APIs. Move with a script that you can run more than once:
- List your applications or tenants and endpoints with the provider's API: Svix applications and endpoints, Hookdeck connections and destinations, or Convoy projects and endpoints.
- Create a Motor tenant for each, with the provider's ID as
external_id, and its endpoints, each with a stable Idempotency-Key such as migrate-endpoint-<old ID>, so a rerun creates nothing twice. - Create your event types in Motor's catalog. For inbound traffic (Svix Ingest, Hookdeck sources, Convoy sources), create Motor sources and enter each provider's signing secret yourself; Motor never fetches credentials from another vendor.
- Send a paid test message to each endpoint with
POST /endpoints/wde_.../test. - Publish each event to both providers for a while, with your own event ID in the payload, so receivers can ignore the copy.
- Switch your publisher to Motor and stop the old one.
Tell your receivers about two changes: Motor signs with Standard Webhooks headers and new whsec_ keys (Convoy and Hookdeck customers change their verification code; Svix customers keep it and update the key), and message IDs change, so deduplicate on your own event ID during the switch.
Rates and settings
GET/webhooks/capabilities
Get capabilities
Returns the region, delivery addresses, source verifiers, content types, hard bounds, and your team's effective limits.
Responses
200The capabilities.
{
"service_id": "webhooks",
"region": "us-east-1",
"egress_ipv4_cidrs": [],
"ipv6_delivery": false,
"verification_kinds": [
"standard_webhooks",
"stripe",
"github",
"hmac",
"shopify",
"slack",
"twilio",
"bearer"
],
"content_types": [
"application/json",
"text/plain",
"application/octet-stream",
"application/x-www-form-urlencoded"
],
"limits": {
"max_body_bytes": {
"value": 1048576,
"unit": "bytes"
},
"max_fanout_endpoints": {
"value": 100,
"unit": "endpoints"
},
"max_transformed_bytes_per_message": {
"value": 8388608,
"unit": "bytes"
},
"max_attempts": {
"value": 9,
"unit": "attempts"
},
"delivery_window_hours": {
"value": 72,
"unit": "hours"
},
"max_schedule_days": {
"value": 7,
"unit": "days"
},
"max_endpoints_per_tenant": {
"value": 100,
"unit": "endpoints"
},
"read_requests_per_second": {
"value": 100,
"unit": "requests per second"
},
"config_writes_per_second": {
"value": 20,
"unit": "requests per second"
},
"admission_rate": {
"value": 100,
"unit": "messages per second",
"measured": false
},
"source_requests_per_second": {
"value": 50,
"unit": "requests per second"
},
"dispatch_rate": {
"value": 1000,
"unit": "attempts per second",
"measured": false
},
"max_concurrency": {
"value": 3,
"unit": "requests"
},
"max_endpoint_concurrency": {
"value": 3,
"unit": "requests"
},
"service_concurrency": {
"value": 6,
"unit": "requests across all teams"
},
"max_tenants": {
"value": 10000,
"unit": "tenants"
},
"max_event_types": {
"value": 1000,
"unit": "event types"
},
"max_endpoints": {
"value": 10000,
"unit": "endpoints"
},
"max_sources": {
"value": 1000,
"unit": "sources"
},
"max_pending_deliveries": {
"value": 100000,
"unit": "deliveries"
},
"max_content_bytes": {
"value": 10737418240,
"unit": "bytes"
},
"max_active_replays": {
"value": 10,
"unit": "jobs"
}
},
"rate_version": "2026-10-08"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Get rates
Returns the published rates. webhooks.ingest_64k is $0.00001 for each 64 KiB of an accepted message's body, at least 1 unit, charged even when the message has no destinations. webhooks.delivery_16k is $0.00005 for each 16 KiB of each delivery generation's body, at least 1 unit per endpoint. Automatic retries are free; a replay is a new paid delivery generation; an endpoint test pays ingestion and delivery. Accepted work keeps the rate version that priced it. There is no free allowance. Usage by meter is at GET /meta/usage.
Responses
200The rates.
{
"currency": "USD",
"version": "2026-10-08",
"effective_at": "2026-10-08T00:00:00Z",
"meters": [
{
"id": "webhooks.ingest_64k",
"unit": "unit",
"unit_bytes": 65536,
"unit_price_usd": "0.00001"
},
{
"id": "webhooks.delivery_16k",
"unit": "unit",
"unit_bytes": 16384,
"unit_price_usd": "0.00005"
}
]
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Get settings
Returns your team's retention and logging settings and its effective limits. The ETag is "wd:settings:<configuration_version>".
Responses
200The settings.
{
"configuration_version": "1",
"payload_retention_days": 30,
"response_body_logging": false,
"response_retention_days": 7,
"approved_configuration_version": "1",
"limits": {
"max_body_bytes": {
"value": 1048576,
"unit": "bytes"
},
"max_fanout_endpoints": {
"value": 100,
"unit": "endpoints"
},
"max_transformed_bytes_per_message": {
"value": 8388608,
"unit": "bytes"
},
"max_attempts": {
"value": 9,
"unit": "attempts"
},
"delivery_window_hours": {
"value": 72,
"unit": "hours"
},
"max_schedule_days": {
"value": 7,
"unit": "days"
},
"max_endpoints_per_tenant": {
"value": 100,
"unit": "endpoints"
},
"read_requests_per_second": {
"value": 100,
"unit": "requests per second"
},
"config_writes_per_second": {
"value": 20,
"unit": "requests per second"
},
"admission_rate": {
"value": 100,
"unit": "messages per second",
"measured": false
},
"source_requests_per_second": {
"value": 50,
"unit": "requests per second"
},
"dispatch_rate": {
"value": 1000,
"unit": "attempts per second",
"measured": false
},
"max_concurrency": {
"value": 3,
"unit": "requests"
},
"max_endpoint_concurrency": {
"value": 3,
"unit": "requests"
},
"service_concurrency": {
"value": 6,
"unit": "requests across all teams"
},
"max_tenants": {
"value": 10000,
"unit": "tenants"
},
"max_event_types": {
"value": 1000,
"unit": "event types"
},
"max_endpoints": {
"value": 10000,
"unit": "endpoints"
},
"max_sources": {
"value": 1000,
"unit": "sources"
},
"max_pending_deliveries": {
"value": 100000,
"unit": "deliveries"
},
"max_content_bytes": {
"value": 10737418240,
"unit": "bytes"
},
"max_active_replays": {
"value": 10,
"unit": "jobs"
}
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Update settings
Changes retention and logging. Omitted fields keep their values; an unchanged request keeps the version. A shorter payload_retention_days applies to stored messages at once (bodies of active deliveries stay until they expire); a longer one never restores erased bytes. Limits can't be changed here: spending is bounded by your key and account limits.
Request body{
"payload_retention_days": 14,
"response_body_logging": true
}
Responses
200The settings, with a new ETag if they changed.
{
"configuration_version": "2",
"payload_retention_days": 14,
"response_body_logging": true,
"response_retention_days": 7,
"approved_configuration_version": "1",
"limits": {
"max_body_bytes": {
"value": 1048576,
"unit": "bytes"
},
"max_fanout_endpoints": {
"value": 100,
"unit": "endpoints"
},
"max_transformed_bytes_per_message": {
"value": 8388608,
"unit": "bytes"
},
"max_attempts": {
"value": 9,
"unit": "attempts"
},
"delivery_window_hours": {
"value": 72,
"unit": "hours"
},
"max_schedule_days": {
"value": 7,
"unit": "days"
},
"max_endpoints_per_tenant": {
"value": 100,
"unit": "endpoints"
},
"read_requests_per_second": {
"value": 100,
"unit": "requests per second"
},
"config_writes_per_second": {
"value": 20,
"unit": "requests per second"
},
"admission_rate": {
"value": 100,
"unit": "messages per second",
"measured": false
},
"source_requests_per_second": {
"value": 50,
"unit": "requests per second"
},
"dispatch_rate": {
"value": 1000,
"unit": "attempts per second",
"measured": false
},
"max_concurrency": {
"value": 3,
"unit": "requests"
},
"max_endpoint_concurrency": {
"value": 3,
"unit": "requests"
},
"service_concurrency": {
"value": 6,
"unit": "requests across all teams"
},
"max_tenants": {
"value": 10000,
"unit": "tenants"
},
"max_event_types": {
"value": 1000,
"unit": "event types"
},
"max_endpoints": {
"value": 10000,
"unit": "endpoints"
},
"max_sources": {
"value": 1000,
"unit": "sources"
},
"max_pending_deliveries": {
"value": 100000,
"unit": "deliveries"
},
"max_content_bytes": {
"value": 10737418240,
"unit": "bytes"
},
"max_active_replays": {
"value": 10,
"unit": "jobs"
}
}
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
412If-Match isn't the current settings ETag (revision_conflict).
{
"error": {
"code": "revision_conflict",
"message": "The settings changed since you read them. Read them again and retry with their new ETag."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is invalid or unknown, or If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`payload_retention_days` must be from 1 to 30."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Tenants
Create a tenant
Creates one of your customers. external_id is your ID for it: unique in your team, immutable, and reserved for 90 days after the tenant is deleted. At most 10,000 tenants by default.
Request body{
"external_id": "acct_1042",
"name": "Acme Robotics",
"metadata": {
"plan": "growth"
}
}
Responses
201The tenant.
{
"id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"external_id": "acct_1042",
"name": "Acme Robotics",
"metadata": {
"plan": "growth"
},
"status": "active",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409Another tenant has the external_id, including a deleted one within 90 days (resource_in_use, with resource_id), or the team is at its tenant limit (quota_exceeded). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "resource_in_use",
"message": "Tenant wdt_1aB2cD3eF4gH5iJ6kL7mN8 has the external ID acct_1042. A deleted tenant keeps its external ID for 90 days.",
"resource_id": "wdt_1aB2cD3eF4gH5iJ6kL7mN8"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is missing, invalid, or unknown, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`external_id` must be 1 to 128 characters of letters, digits, `_`, `.`, `:`, and `-`."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
List tenants
Lists tenants, deleted ones included, oldest first.
Responses
200A page of tenants.
{
"data": [
{
"id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"external_id": "acct_1042",
"name": "Acme Robotics",
"metadata": {
"plan": "growth"
},
"status": "active",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422limit is invalid (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_cursor",
"message": "The cursor isn't valid for this list. Use next_cursor from the previous response.",
"field": "cursor"
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/tenants/{id}
Get a tenant
Returns a tenant, active or deleted, with its ETag.
Responses
200The tenant.
{
"id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"external_id": "acct_1042",
"name": "Acme Robotics",
"metadata": {
"plan": "growth"
},
"status": "active",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The tenant doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The tenant doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
PATCH/webhooks/tenants/{id}
Update a tenant
Changes the name or the metadata. Omitted fields keep their values; metadata replaces all pairs. An unchanged request keeps the version.
Request body{
"name": "Acme Robotics Inc."
}
Responses
200The tenant.
{
"id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"external_id": "acct_1042",
"name": "Acme Robotics Inc.",
"metadata": {
"plan": "growth"
},
"status": "active",
"version": "2",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The tenant doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The tenant doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is invalid or unknown, or If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`name` must be from 1 to 128 bytes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
DELETE/webhooks/tenants/{id}
Delete a tenant
Deletes the tenant, its endpoints, and its sources at once: no new messages or captures, queued deliveries are canceled, and its content is erased. Requests already in flight may complete. Deletion can't be undone; charges already made stay.
Responses
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The tenant doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The tenant doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
422If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`If-Match` must be one strong ETag in quotes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Event types
POST/webhooks/event_types
Create an event type
Adds a type to your catalog. Messages need an active type; sources may only emit active types. Names are immutable and unique, archived ones included. At most 1,000 types by default.
Request body{
"name": "invoice.paid",
"description": "An invoice was paid in full.",
"example": {
"invoice_id": "inv_42",
"amount": "49.00"
}
}
Responses
201The event type.
{
"id": "wdy_9QwErT5yUiOp3aSdFgHjKz",
"name": "invoice.paid",
"description": "An invoice was paid in full.",
"example": {
"invoice_id": "inv_42",
"amount": "49.00"
},
"archived": false,
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409The name exists, archived or not (resource_in_use, with resource_id; reactivate an archived type with PATCH and archived: false), or the team is at its limit (quota_exceeded). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "resource_in_use",
"message": "The event type invoice.paid exists.",
"resource_id": "wdy_9QwErT5yUiOp3aSdFgHjKz"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The name is invalid or reserved (webhooks.*, motor.*), a field is invalid or unknown, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`name` can't start with webhooks. or motor., which Motor reserves."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
List event types
Lists your catalog, archived types included, oldest first.
Responses
200A page of event types.
{
"data": [
{
"id": "wdy_9QwErT5yUiOp3aSdFgHjKz",
"name": "invoice.paid",
"description": "An invoice was paid in full.",
"example": {
"invoice_id": "inv_42",
"amount": "49.00"
},
"archived": false,
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422limit is invalid (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`limit` must be from 1 to 200."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/event_types/{id}
Get an event type
Returns an event type with its ETag.
Responses
200The event type.
{
"id": "wdy_9QwErT5yUiOp3aSdFgHjKz",
"name": "invoice.paid",
"description": "An invoice was paid in full.",
"example": {
"invoice_id": "inv_42",
"amount": "49.00"
},
"archived": false,
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The event type doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The event type doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
PATCH/webhooks/event_types/{id}
Update an event type
Changes the description or the example, or reactivates an archived type with archived: false. The name can't change.
Request body{
"archived": false
}
Responses
200The event type.
{
"id": "wdy_9QwErT5yUiOp3aSdFgHjKz",
"name": "invoice.paid",
"description": "An invoice was paid in full.",
"example": {
"invoice_id": "inv_42",
"amount": "49.00"
},
"archived": false,
"version": "3",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The event type doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The event type doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is invalid or unknown, archived is true, or If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`archived` can only be false, which reactivates the type. DELETE archives it."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
DELETE/webhooks/event_types/{id}
Archive an event type
Archives the type: new messages and captures of it are refused with 422 invalid_event_type, while retained messages stay readable and replayable. Archiving an archived type changes nothing.
Responses
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The event type doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The event type doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
422If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`If-Match` must be one strong ETag in quotes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Endpoints
Create an endpoint
Creates a receiver for a tenant's messages and returns its signing_secret once. Every delivery is signed with Standard Webhooks headers (see the service description). At most 100 endpoints per tenant and 10,000 per team by default.
Request body{
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"url": "https://hooks.acme.example/motor?team=7",
"name": "Billing receiver",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"headers": {
"X-Acme-Tenant": "1042"
},
"authorization": "Bearer acme_live_9f8e7d6c"
}
Responses
201The endpoint, with signing_secret. An idempotent replay shows the secret only to the key that created it, while that secret is current.
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/motor?team=7",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "enabled",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 1,
"old_secret_expires_at": null,
"health": {
"circuit": "closed",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "1",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z",
"signing_secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSwq8uEPmRUQ0Y="
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409The team or the tenant is at its endpoint limit (quota_exceeded). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "quota_exceeded",
"message": "Your team has reached its limit of 100 endpoints for this tenant. Delete some, or contact support@motor.dev to raise the limit."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is missing, invalid, or unknown, the tenant isn't active, max_concurrency is over your cap, or Idempotency-Key is missing (invalid_field); or the URL's host is private or Motor's (unsafe_url). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "unsafe_url",
"message": "`url` can't be a private, loopback, link-local, or reserved address.",
"field": "url"
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
List endpoints
Lists endpoints that aren't deleted, oldest first.
Responses
200A page of endpoints.
{
"data": [
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/motor?team=7",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "enabled",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 1,
"old_secret_expires_at": null,
"health": {
"circuit": "closed",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "1",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A filter is invalid or names another team's tenant (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`status` must be enabled, paused, or disabled."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/endpoints/{id}
Get an endpoint
Returns an endpoint with its URL, health, and ETag. The signing secret, header values, and authorization are never shown.
Responses
200The endpoint.
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/motor?team=7",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "enabled",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 1,
"old_secret_expires_at": null,
"health": {
"circuit": "closed",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "1",
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
PATCH/webhooks/endpoints/{id}
Update an endpoint
Changes the configuration; the tenant can't change. Omitted fields keep their values, null clears filter, transform, and authorization, and arrays and objects replace in full. A URL with a new host must come with authorization (or null) and headers (or {}), so that credentials never move to another host by omission. A new URL resets health. Every pending delivery, retries and replays included, goes to the current URL with the current credentials at its next attempt; queued bytes never change. A disabled endpoint stays disabled until /resume.
Request body{
"url": "https://hooks.acme.example/v2/motor",
"status": "paused"
}
Responses
200The endpoint.
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/v2/motor",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "paused",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 1,
"old_secret_expires_at": null,
"health": {
"circuit": "closed",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "2",
"version": "4",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
409status: enabled for an endpoint Motor disabled (invalid_state): use /resume.
{
"error": {
"code": "invalid_state",
"message": "Motor disabled this endpoint. Call POST /endpoints/wde_6mNbVc8xZaS2dFgH4jKlQw/resume to enable it."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is invalid or unknown, a new host lacks authorization and headers, or If-Match is malformed (invalid_field); or the URL is unsafe (unsafe_url). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`url` changes the host, so send authorization (or null) and headers (or {}) too. Motor doesn't move credentials to a new host.",
"field": "url"
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
DELETE/webhooks/endpoints/{id}
Delete an endpoint
Deletes the endpoint: it gets no new messages, nothing more is claimed, and its unclaimed deliveries are canceled. A request already in flight can't be recalled.
Responses
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
422If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`If-Match` must be one strong ETag in quotes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/endpoints/{id}/rotate_secret
Rotate the signing secret
Makes a new signing secret and returns it once. During overlap_seconds (86,400 by default), deliveries carry a v1 signature for both secrets; 0 retires every old secret at once. Another nonzero rotation during an overlap is 409.
Request body{
"overlap_seconds": 3600
}
Responses
200The endpoint, with the new signing_secret. An idempotent replay shows it only to the key that rotated it, while it's current.
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/motor?team=7",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "enabled",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 2,
"old_secret_expires_at": "2026-10-10T18:22:01Z",
"health": {
"circuit": "closed",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "1",
"version": "3",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z",
"signing_secret": "whsec_c2VjcmV0LWtleS1mb3ItZXhhbXBsZXMtb25seS0xMjM="
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
409The previous secret is still in its overlap and overlap_seconds isn't 0 (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The previous signing key is still valid until 2026-10-10T18:22:01Z. Wait for the overlap to end, or rotate with overlap_seconds: 0 to retire every old key at once."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422overlap_seconds is invalid, a field is unknown, or a header is missing or malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`overlap_seconds` must be from 0 to 86400."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
POST/webhooks/endpoints/{id}/resume
Resume an endpoint
Checks the URL again, enables the endpoint, resets its circuit to half-open and lets one probe go. Use it for an endpoint that Motor disabled or whose circuit is open. Finished deliveries stay finished: replay them if you still need them.
Responses
200The endpoint. An idempotent replay returns only {"id", "status"}.
{
"id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Billing receiver",
"description": "",
"url": "https://hooks.acme.example/motor?team=7",
"events": [
"invoice.*"
],
"filter": {
"all": [
{
"path": "/amount",
"op": "prefix",
"value": "4"
}
]
},
"transform": null,
"transform_version": "1",
"status": "enabled",
"disabled_reason": null,
"rate_per_second": 50,
"max_concurrency": 3,
"retry_delays_seconds": [
15,
60,
300,
900,
3600,
14400,
43200,
86400
],
"timeout_seconds": 10,
"headers": [
"x-acme-tenant"
],
"has_authorization": true,
"metadata": {},
"secret_version": 1,
"old_secret_expires_at": null,
"health": {
"circuit": "half_open",
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"next_probe_at": null
},
"queued_count": 0,
"url_revision": "1",
"version": "5",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
409Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "idempotency_conflict",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new operation."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The URL is no longer safe (unsafe_url), the body has a field, or a header is missing or malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "unsafe_url",
"message": "`url` can't be a private or Motor host.",
"field": "url"
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
POST/webhooks/endpoints/{id}/test
Send a test message
Sends a motor.test message to this endpoint's real receiver only. It skips the endpoint's event types and filter, but uses its authentication, signing, retries, and billing: it costs ingestion ($0.00001 per 64 KiB, at least 1 unit) plus delivery ($0.00005 per 16 KiB, at least 1 unit). Paused and disabled endpoints are refused; a test never resumes them.
Request body{
"payload": {
"test": true
}
}
Responses
202The accepted test message.
{
"id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"event_type": "motor.test",
"external_id": null,
"origin": "test",
"source_id": null,
"status": "queued",
"body_bytes": 11,
"body_sha256": "7bc2e5c2c4b0f0b8d1b9a9e1c3d2f4a6b8c0e2f4a6b8c0d2e4f6a8b0c2d4e6f8",
"content_type": "application/json",
"deliver_at": "2026-10-09T18:22:01Z",
"expires_at": "2026-10-12T18:22:01Z",
"retained_until": "2026-11-08T18:22:01Z",
"content_available": true,
"delivery_counts": {
"scheduled": 0,
"queued": 1,
"in_flight": 0,
"retry_wait": 0,
"succeeded": 0,
"failed": 0,
"expired": 0,
"canceled": 0
},
"price": {
"ingest_units": 1,
"delivery_units": 1,
"total_usd": "0.00006",
"rate_version": "2026-10-08"
},
"redacted": false,
"redaction_status": null,
"redacted_at": null,
"metadata": {},
"created_at": "2026-10-09T18:22:01Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
402No payment card is on the account (billing_required), or the charge would exceed the paying key's monthly spending limit (spending_limit).
{
"error": {
"code": "spending_limit",
"message": "This work would exceed the paying API key's monthly spending limit. Raise the limit on the API keys page, or wait until the next billing month starts at 00:00 UTC."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The endpoint doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The endpoint doesn't exist."
}
}
409The endpoint is paused or disabled (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The endpoint is paused. Enable it before you send a test message."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The payload or a header is invalid, or a field is unknown (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`payload` isn't valid JSON."
}
}
429Too many admissions: 100 a second by default, bursts of twice that (rate_limited), or the team's pending deliveries or stored content is at its limit (queue_full).
{
"error": {
"code": "queue_full",
"message": "Your team has 100000 deliveries waiting, and its limit is 100000. Retry later."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
Messages
Send a message
Accepts a message for one tenant and fans it out to the tenant's matching enabled and paused endpoints (at most 100, with at most 8 MiB of transformed bodies in all). Which endpoints receive it, and the bytes each one gets, are fixed now; endpoints created later receive nothing. payload is delivered as the exact bytes you sent, with insignificant whitespace removed: key order and number spellings stay. Admission is atomic: a transform failure or a limit rejects the whole message.
Pricing: webhooks.ingest_64k is $0.00001 for each 64 KiB of the body, at least 1 unit, charged even with no matching endpoint; webhooks.delivery_16k is $0.00005 for each 16 KiB of each endpoint's body, at least 1 unit. Up to 9 attempts are included; retries are free. Nothing is refunded once accepted.
Request body{
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"event_type": "invoice.paid",
"payload": {
"invoice_id": "inv_42",
"amount": "49.00"
},
"external_id": "invoice-paid-42",
"metadata": {
"order": "42"
}
}
Responses
202The accepted message with its price. Delivery happens afterwards; its failures appear on the deliveries.
{
"id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"event_type": "invoice.paid",
"external_id": "invoice-paid-42",
"origin": "api",
"source_id": null,
"status": "queued",
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"deliver_at": "2026-10-09T18:22:01Z",
"expires_at": "2026-10-12T18:22:01Z",
"retained_until": "2026-11-08T18:22:01Z",
"content_available": true,
"delivery_counts": {
"scheduled": 0,
"queued": 2,
"in_flight": 0,
"retry_wait": 0,
"succeeded": 0,
"failed": 0,
"expired": 0,
"canceled": 0
},
"price": {
"ingest_units": 1,
"delivery_units": 2,
"total_usd": "0.00011",
"rate_version": "2026-10-08"
},
"redacted": false,
"redaction_status": null,
"redacted_at": null,
"metadata": {
"order": "42"
},
"created_at": "2026-10-09T18:22:01Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
402No payment card is on the account (billing_required), or the charge would exceed the paying key's monthly spending limit (spending_limit).
{
"error": {
"code": "spending_limit",
"message": "This work would exceed the paying API key's monthly spending limit. Raise the limit on the API keys page, or wait until the next billing month starts at 00:00 UTC."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "idempotency_conflict",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new operation."
}
}
413The body, the payload, or the decoded body_base64 is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The payload must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The event type isn't active (invalid_event_type); an endpoint's transform failed (transform_failed, with resource_id); or a field is invalid, the message matches more than 100 endpoints or 8 MiB, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_event_type",
"message": "The event type invoice.payed isn't an active type of your catalog. Create it with POST /webhooks/event_types first.",
"field": "event_type"
}
}
429Too many admissions: 100 a second by default, bursts of twice that (rate_limited), or the team's pending deliveries or stored content is at its limit (queue_full).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't store the message, or the tenant's configuration kept changing (temporarily_unavailable). Retry with the same key.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't store the message. Retry in a few seconds."
}
}
List messages
Lists messages created in a window of at most 30 days, oldest first, without their bodies. The status filter applies within each page, so a page may have fewer items than limit while next_cursor is set.
Responses
200A page of messages.
{
"data": [
{
"id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"event_type": "invoice.paid",
"external_id": "invoice-paid-42",
"origin": "api",
"source_id": null,
"status": "queued",
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"deliver_at": "2026-10-09T18:22:01Z",
"expires_at": "2026-10-12T18:22:01Z",
"retained_until": "2026-11-08T18:22:01Z",
"content_available": true,
"delivery_counts": {
"scheduled": 0,
"queued": 2,
"in_flight": 0,
"retry_wait": 0,
"succeeded": 0,
"failed": 0,
"expired": 0,
"canceled": 0
},
"price": {
"ingest_units": 1,
"delivery_units": 2,
"total_usd": "0.00011",
"rate_version": "2026-10-08"
},
"redacted": false,
"redaction_status": null,
"redacted_at": null,
"metadata": {
"order": "42"
},
"created_at": "2026-10-09T18:22:01Z"
}
],
"next_cursor": "eyJ2IjoxLCJzIjoiOGYzYSIsImEiOiIxNzkxNTY5NzIxMDAwMDAwIDQyIn0.Zm9vYmFyYmF6"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A filter or time is invalid, the window is longer than 30 days (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`created_before` must be at most 30 days after created_after."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/messages/{id}
Get a message
Returns a message's metadata, status, counts, and price.
Responses
200The message.
{
"id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"event_type": "invoice.paid",
"external_id": "invoice-paid-42",
"origin": "api",
"source_id": null,
"status": "queued",
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"deliver_at": "2026-10-09T18:22:01Z",
"expires_at": "2026-10-12T18:22:01Z",
"retained_until": "2026-11-08T18:22:01Z",
"content_available": true,
"delivery_counts": {
"scheduled": 0,
"queued": 2,
"in_flight": 0,
"retry_wait": 0,
"succeeded": 0,
"failed": 0,
"expired": 0,
"canceled": 0
},
"price": {
"ingest_units": 1,
"delivery_units": 2,
"total_usd": "0.00011",
"rate_version": "2026-10-08"
},
"redacted": false,
"redaction_status": null,
"redacted_at": null,
"metadata": {
"order": "42"
},
"created_at": "2026-10-09T18:22:01Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The message doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The message doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/messages/{id}/content
Get a message's body
Returns the original accepted bytes with their content type, until the message is redacted or passes retained_until.
Responses
200The body, with the message's Content-Type.
{
"invoice_id": "inv_42",
"amount": "49.00"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The message doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The message doesn't exist."
}
}
410The body was redacted or passed its retention (payload_expired). The metadata stays.
{
"error": {
"code": "payload_expired",
"message": "The message's content passed its retention and is gone. Its metadata stays."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't read the body (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't read the content. Retry in a few seconds."
}
}
POST/webhooks/messages/{id}/cancel
Cancel a message
Cancels the message's original deliveries that no attempt has claimed. Attempts in flight finish, but their retries are canceled. Replays aren't affected. Repeating it has no new effect. Nothing is refunded.
Responses
200What the cancellation did.
{
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"canceled_count": 1,
"in_flight_count": 1,
"already_terminal_count": 0
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The message doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The message doesn't exist."
}
}
409Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "idempotency_conflict",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new operation."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The body has a field, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Idempotency-Key` is required for this request."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/messages/{id}/redact
Redact a message
Erases the message's body and every transformed copy irreversibly, and cancels all its pending deliveries, replays included. Reads and replays of the content stop at once; the stored bytes go within 24 hours, and the message's redaction_status becomes complete. Metadata and charges stay.
Responses
202Redaction started.
{
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"status": "redacting",
"redacted_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The message doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The message doesn't exist."
}
}
409Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "idempotency_conflict",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new operation."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The body has a field, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Idempotency-Key` is required for this request."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Preview an endpoint
Shows whether an endpoint's current filter matches a body and what its transform makes of it. Nothing is stored, sent, or charged, and no Idempotency-Key is needed. It doesn't check event types or the endpoint's status.
Request body{
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"payload": {
"invoice_id": "inv_42",
"amount": "49.00",
"internal": true
}
}
Responses
200The preview.
{
"matches": true,
"body_base64": "eyJhbW91bnQiOiI0OS4wMCIsImludm9pY2VfaWQiOiJpbnZfNDIifQ==",
"content_type": "application/json",
"body_bytes": 40,
"delivery_units": 1,
"transform_version": "2",
"errors": []
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422endpoint_id isn't an endpoint of your team, or the payload is invalid (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`endpoint_id` must be the ID of an endpoint of your team."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Deliveries
List deliveries
Lists deliveries created in a window of at most 30 days, oldest first. With message_id, the window is ignored. status=failed and status=expired list the dead letters.
Responses
200A page of deliveries.
{
"data": [
{
"id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"generation": 0,
"replay_of": null,
"replay_id": null,
"transform_version": "1",
"configuration": "original",
"status": "failed",
"blocked_reason": null,
"attempt_count": 9,
"next_attempt_at": null,
"expires_at": "2026-10-12T18:22:01Z",
"completed_at": "2026-10-12T11:40:09Z",
"reason": "http_500",
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"rate_version": "2026-10-08",
"delivery_units": 1,
"price_usd": "0.00005",
"cancel_requested": false,
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-12T11:40:09Z"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A filter or time is invalid or names another team's resource, the window is longer than 30 days (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`status` must be scheduled, queued, in_flight, retry_wait, succeeded, failed, expired, or canceled."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/deliveries/{id}
Get a delivery
Returns a delivery.
Responses
200The delivery.
{
"id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"generation": 0,
"replay_of": null,
"replay_id": null,
"transform_version": "1",
"configuration": "original",
"status": "retry_wait",
"blocked_reason": null,
"attempt_count": 2,
"next_attempt_at": "2026-10-09T18:28:16Z",
"expires_at": "2026-10-12T18:22:01Z",
"completed_at": null,
"reason": null,
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"rate_version": "2026-10-08",
"delivery_units": 1,
"price_usd": "0.00005",
"cancel_requested": false,
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:23:16Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/deliveries/{id}/content
Get a delivery's body
Returns the exact outbound bytes of the delivery, with its content type: the message's bytes, or its transformed bytes.
Responses
200The body, with the delivery's Content-Type.
{
"invoice_id": "inv_42",
"amount": "49.00"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
410The message was redacted or its body passed its retention (payload_expired).
{
"error": {
"code": "payload_expired",
"message": "The message was redacted. Its content is gone."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't read the body (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't read the content. Retry in a few seconds."
}
}
GET/webhooks/deliveries/{id}/attempts
List a delivery's attempts
Lists the delivery's attempts, at most 9, in order. There is one page: next_cursor is always null.
Responses
200The attempts.
{
"data": [
{
"id": "wda_1qAzWs4XeDc7RfVtGb9YhN",
"delivery_id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"number": 2,
"claim_generation": 2,
"started_at": "2026-10-09T18:23:16Z",
"finished_at": "2026-10-09T18:23:16Z",
"duration_ms": 412,
"http_status": 503,
"result": "retryable",
"error_code": null,
"response_headers": {
"content-type": "text/plain",
"retry-after": "60"
},
"response_body_base64": null,
"response_truncated": false,
"signature_timestamp": 1791569996,
"destination_host": "hooks.acme.example",
"endpoint_version": "1",
"url_revision": "1",
"probe": false
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/attempts/{id}
Get an attempt
Returns an attempt with its timing and the stored part of the response.
Responses
200The attempt.
{
"id": "wda_1qAzWs4XeDc7RfVtGb9YhN",
"delivery_id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"number": 2,
"claim_generation": 2,
"started_at": "2026-10-09T18:23:16Z",
"finished_at": "2026-10-09T18:23:16Z",
"duration_ms": 412,
"http_status": 503,
"result": "retryable",
"error_code": null,
"response_headers": {
"content-type": "text/plain",
"retry-after": "60"
},
"response_body_base64": null,
"response_truncated": false,
"signature_timestamp": 1791569996,
"destination_host": "hooks.acme.example",
"endpoint_version": "1",
"url_revision": "1",
"probe": false
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The attempt doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The attempt doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/deliveries/{id}/cancel
Cancel a delivery
Stops further attempts. A waiting delivery becomes canceled; one in flight finishes, and in_flight is true because only its retries are stopped. Canceling a canceled delivery changes nothing. Nothing is refunded.
Responses
200The delivery, with in_flight.
{
"id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"generation": 0,
"replay_of": null,
"replay_id": null,
"transform_version": "1",
"configuration": "original",
"status": "canceled",
"blocked_reason": null,
"attempt_count": 2,
"next_attempt_at": null,
"expires_at": "2026-10-12T18:22:01Z",
"completed_at": "2026-10-09T18:24:30Z",
"reason": "canceled",
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"rate_version": "2026-10-08",
"delivery_units": 1,
"price_usd": "0.00005",
"cancel_requested": true,
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:24:30Z",
"in_flight": false
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
409The delivery already succeeded, failed, or expired (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The delivery is already succeeded. Only waiting deliveries can be canceled."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The body has a field, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Idempotency-Key` is required for this request."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/deliveries/{id}/replay
Replay a delivery
Creates a new generation of a finished delivery, with the same message ID (webhook-id) and a new delivery ID, sent to the endpoint's current URL with its current credentials and a new 72-hour deadline. original resends the frozen bytes; current applies the endpoint's current transform to the message and skips its filter. The endpoint must be enabled or paused, and the body retained and not redacted.
Pricing: a replay is a new paid delivery generation, $0.00005 for each 16 KiB of its body, at least 1 unit; no new ingestion. Its retries are free.
Request body{
"configuration": "original"
}
Responses
202The new delivery.
{
"id": "wdd_7uIoPa2SdF5gHjKl9ZxCvB",
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"generation": 1,
"replay_of": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"replay_id": null,
"transform_version": "1",
"configuration": "original",
"status": "queued",
"blocked_reason": null,
"attempt_count": 0,
"next_attempt_at": "2026-10-13T09:00:00Z",
"expires_at": "2026-10-16T09:00:00Z",
"completed_at": null,
"reason": null,
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"rate_version": "2026-10-08",
"delivery_units": 1,
"price_usd": "0.00005",
"cancel_requested": false,
"created_at": "2026-10-13T09:00:00Z",
"updated_at": "2026-10-13T09:00:00Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
402No payment card is on the account (billing_required), or the charge would exceed the paying key's monthly spending limit (spending_limit).
{
"error": {
"code": "spending_limit",
"message": "This work would exceed the paying API key's monthly spending limit. Raise the limit on the API keys page, or wait until the next billing month starts at 00:00 UTC."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
409The delivery hasn't finished, or its tenant is deleted or its endpoint disabled or deleted (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The delivery is retry_wait. Only a finished delivery can be replayed."
}
}
410The message was redacted or its body passed its retention (payload_expired).
{
"error": {
"code": "payload_expired",
"message": "The message's content passed its retention."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The current transform failed (transform_failed), or a field is invalid (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "transform_failed",
"message": "The endpoint's current transform failed: /customer/id is required."
}
}
429Too many admissions (rate_limited), or the team's pending deliveries or stored content is at its limit (queue_full).
{
"error": {
"code": "queue_full",
"message": "Your team has too many deliveries waiting. Retry later."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
POST/webhooks/deliveries/{id}/retry_now
Retry a delivery now
Makes a delivery that waits to retry due now, within its existing attempts. A receiver's Retry-After for the same URL still holds, and pacing, concurrency, and an open circuit still apply. It creates no generation and no charge, and resets neither attempts nor the deadline. A finished delivery needs a paid replay instead.
Responses
200The delivery, with its effective next_attempt_at and blocked_reason.
{
"id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"generation": 0,
"replay_of": null,
"replay_id": null,
"transform_version": "1",
"configuration": "original",
"status": "retry_wait",
"blocked_reason": null,
"attempt_count": 2,
"next_attempt_at": "2026-10-09T18:24:30Z",
"expires_at": "2026-10-12T18:22:01Z",
"completed_at": null,
"reason": null,
"body_bytes": 41,
"body_sha256": "3f0a2b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a",
"content_type": "application/json",
"rate_version": "2026-10-08",
"delivery_units": 1,
"price_usd": "0.00005",
"cancel_requested": false,
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:24:30Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The delivery doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The delivery doesn't exist."
}
}
409The delivery isn't retry_wait (delivery_not_retryable), or something blocks it (retry_blocked, with reason): a deleted tenant, a paused or disabled endpoint, an unavailable payload, a canceled message, no attempts left, expiry, or a suspended account. Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "retry_blocked",
"message": "Motor can't retry this delivery now: endpoint_paused.",
"reason": "endpoint_paused"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The body has a field, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Idempotency-Key` is required for this request."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Replays
Replay deliveries in bulk
Selects, for one endpoint, the newest generation of each message created in a window of at most 30 days whose status is in statuses, and replays them in batches as new paid generations (original bytes or the current transform captured now). Each batch is charged when it's created ($0.00005 per 16 KiB unit, at least 1 per delivery); the job stops as failed if the next batch would exceed your spending limit (spending_limit) or max_cost_usd (cost_ceiling_exceeded), or after 24 hours. An empty selection completes at once and costs nothing. At most 10 active replays by default.
Request body{
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"created_after": "2026-10-01T00:00:00Z",
"created_before": "2026-10-09T00:00:00Z",
"max_cost_usd": "5.00"
}
Responses
202The replay, with selected_count.
{
"id": "wdr_3eDcRf6VtGb8YhNuJm2IkO",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"created_after": "2026-10-01T00:00:00Z",
"created_before": "2026-10-09T00:00:00Z",
"statuses": [
"failed",
"expired"
],
"configuration": "original",
"transform_version": "1",
"max_deliveries": 1000,
"max_cost_usd": "5.00",
"status": "queued",
"reason": null,
"selected_count": 312,
"created_count": 0,
"skipped_count": 0,
"pending_count": 312,
"charged_usd": "0.00",
"rate_version": "2026-10-08",
"deadline": "2026-10-10T18:22:01Z",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z",
"completed_at": null
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
402No payment card is on the account (billing_required).
{
"error": {
"code": "billing_required",
"message": "Add a payment card on the billing page before sending webhooks."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409The endpoint is disabled (invalid_state), or the team has its maximum of active replays (quota_exceeded). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "quota_exceeded",
"message": "Your team has reached its limit of 10 active replays. Delete some, or contact support@motor.dev to raise the limit."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is missing or invalid, the window is longer than 30 days, more deliveries match than max_deliveries, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "The window has 1412 matching deliveries, more than max_deliveries (1000). Narrow the window or raise max_deliveries, up to 10000.",
"field": "max_deliveries"
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
List replays
Lists replays submitted in a window of at most 30 days, oldest first.
Responses
200A page of replays.
{
"data": [
{
"id": "wdr_3eDcRf6VtGb8YhNuJm2IkO",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"created_after": "2026-10-01T00:00:00Z",
"created_before": "2026-10-09T00:00:00Z",
"statuses": [
"failed",
"expired"
],
"configuration": "original",
"transform_version": "1",
"max_deliveries": 1000,
"max_cost_usd": "5.00",
"status": "queued",
"reason": null,
"selected_count": 312,
"created_count": 0,
"skipped_count": 0,
"pending_count": 312,
"charged_usd": "0.00",
"rate_version": "2026-10-08",
"deadline": "2026-10-10T18:22:01Z",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z",
"completed_at": null
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A time is invalid, the window is longer than 30 days (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`created_after` must be before created_before."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/replays/{id}
Get a replay
Returns a replay with its counts and charge so far.
Responses
200The replay.
{
"id": "wdr_3eDcRf6VtGb8YhNuJm2IkO",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"created_after": "2026-10-01T00:00:00Z",
"created_before": "2026-10-09T00:00:00Z",
"statuses": [
"failed",
"expired"
],
"configuration": "original",
"transform_version": "1",
"max_deliveries": 1000,
"max_cost_usd": "5.00",
"status": "queued",
"reason": null,
"selected_count": 312,
"created_count": 0,
"skipped_count": 0,
"pending_count": 312,
"charged_usd": "0.00",
"rate_version": "2026-10-08",
"deadline": "2026-10-10T18:22:01Z",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z",
"completed_at": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The replay doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The replay doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/replays/{id}/items
List a replay's items
Lists the result for each selected delivery, in selection order. Items not reached yet are pending, or skipped with the stop reason once the replay stopped.
Responses
200A page of items.
{
"data": [
{
"number": 0,
"original_delivery_id": "wdd_5gHjKl8ZxCv3BnMq7WeRtY",
"delivery_id": "wdd_7uIoPa2SdF5gHjKl9ZxCvB",
"status": "created",
"reason": null,
"price_usd": "0.00005"
}
],
"next_cursor": "eyJ2IjoxLCJzIjoiOGYzYSIsImEiOiIxNzkxNTY5NzIxMDAwMDAwIDQyIn0.Zm9vYmFyYmF6"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The replay doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The replay doesn't exist."
}
}
422limit is invalid (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_cursor",
"message": "The cursor isn't valid for this list. Use next_cursor from the previous response.",
"field": "cursor"
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/replays/{id}/cancel
Cancel a replay
Stops creating deliveries. Deliveries already created stay, and so do their charges; the remaining items become skipped. Canceling a finished replay changes nothing.
Responses
200The replay.
{
"id": "wdr_3eDcRf6VtGb8YhNuJm2IkO",
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"created_after": "2026-10-01T00:00:00Z",
"created_before": "2026-10-09T00:00:00Z",
"statuses": [
"failed",
"expired"
],
"configuration": "original",
"transform_version": "1",
"max_deliveries": 1000,
"max_cost_usd": "5.00",
"status": "canceled",
"reason": "canceled",
"selected_count": 312,
"created_count": 200,
"skipped_count": 112,
"pending_count": 0,
"charged_usd": "0.01",
"rate_version": "2026-10-08",
"deadline": "2026-10-10T18:22:01Z",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:30:00Z",
"completed_at": "2026-10-09T18:30:00Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The replay doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The replay doesn't exist."
}
}
409Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "idempotency_conflict",
"message": "This Idempotency-Key was used for a different request. Use a new key for a new operation."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The body has a field, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Idempotency-Key` is required for this request."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Sources
Create a source
Creates a verified receive URL for another provider's webhooks, and returns receive_url once. Each verified request becomes a message of the source's tenant, priced like POST /messages and paid by billing_key_id. Stripe signs test and live events with different secrets: create one source for each. At most 1,000 sources by default.
Request body{
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret": "whsec_test_51NcQz7T"
},
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe."
}
Responses
201The source, with receive_url. An idempotent replay shows it only to the key that created it, while that token is current.
{
"id": "wds_8kJhGf3dSa7QwErTy5UiOp",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret_hint": "Qz7T",
"secret_version": 1,
"old_secret_expires_at": null
},
"receive_path": "/webhooks/receive/…x9Qz",
"token_version": 1,
"status": "enabled",
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe.",
"dedup_header": null,
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z",
"receive_url": "https://api.motor.dev/webhooks/receive/Jx2kq8V0rW5nTq3mZbLs0aYc1dHf7gPe4uKiO6x9Qz"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
409The team is at its source limit (quota_exceeded). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "quota_exceeded",
"message": "Your team has reached its limit of 1000 sources. Delete some, or contact support@motor.dev to raise the limit."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is missing, invalid, or unknown, the event type isn't active, the secret doesn't fit the kind, or Idempotency-Key is missing (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`verification.secret` must be from 16 to 256 UTF-8 bytes."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
List sources
Lists sources that aren't deleted, oldest first.
Responses
200A page of sources.
{
"data": [
{
"id": "wds_2wSxEdC4rFvTgB6yHnUjM8",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Shopify-style HMAC",
"verification": {
"kind": "hmac",
"algorithm": "sha256",
"header": "x-acme-signature",
"encoding": "hex",
"prefix": "sha256=",
"signed_content": [
{
"header": "x-acme-timestamp"
},
{
"literal": "."
},
"body"
],
"timestamp_header": "x-acme-timestamp",
"tolerance_seconds": 300,
"secret_hint": "",
"secret_version": 1,
"old_secret_expires_at": null
},
"receive_path": "/webhooks/receive/…x9Qz",
"token_version": 1,
"status": "enabled",
"event_type": "acme.order",
"event_type_path": null,
"event_type_prefix": "",
"dedup_header": "x-acme-delivery",
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A filter is invalid (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`status` must be enabled or paused."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
GET/webhooks/sources/{id}
Get a source
Returns a source with its ETag. Its secret and receive URL are never shown.
Responses
200The source.
{
"id": "wds_8kJhGf3dSa7QwErTy5UiOp",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret_hint": "Qz7T",
"secret_version": 1,
"old_secret_expires_at": null
},
"receive_path": "/webhooks/receive/…x9Qz",
"token_version": 1,
"status": "enabled",
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe.",
"dedup_header": null,
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "1",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:01Z"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The source doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The source doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
PATCH/webhooks/sources/{id}
Update a source
Changes the name, status, event type mapping, deduplication header, billing key, or metadata. The tenant and the verifier can't change: rotate the secret with rotate_secret, or create a new source.
Request body{
"status": "paused"
}
Responses
200The source.
{
"id": "wds_8kJhGf3dSa7QwErTy5UiOp",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret_hint": "Qz7T",
"secret_version": 1,
"old_secret_expires_at": null
},
"receive_path": "/webhooks/receive/…x9Qz",
"token_version": 1,
"status": "paused",
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe.",
"dedup_header": null,
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "2",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The source doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The source doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422A field is invalid or unknown, or If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`billing_key_id` must be the ID of an active API key of your team that may use webhooks."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
DELETE/webhooks/sources/{id}
Delete a source
Deletes the source: its receive URLs and secrets stop working at once. Accepted messages stay with their tenant and follow normal retention.
Responses
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The source doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The source doesn't exist."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
422If-Match is malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`If-Match` must be one strong ETag in quotes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
POST/webhooks/sources/{id}/rotate_token
Rotate the receive URL
Makes a new receive URL and returns it once. The old URL keeps working for overlap_seconds (86,400 by default); 0 retires it at once. Another nonzero rotation during an overlap is 409.
Request body{
"overlap_seconds": 86400
}
Responses
200The source, with the new receive_url. An idempotent replay shows it only to the key that rotated it, while it's current.
{
"id": "wds_8kJhGf3dSa7QwErTy5UiOp",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret_hint": "Qz7T",
"secret_version": 1,
"old_secret_expires_at": null
},
"receive_path": "/webhooks/receive/…Lm3P",
"token_version": 2,
"status": "enabled",
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe.",
"dedup_header": null,
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "2",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z",
"receive_url": "https://api.motor.dev/webhooks/receive/aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8Lm3P"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The source doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The source doesn't exist."
}
}
409The previous URL is still in its overlap and overlap_seconds isn't 0 (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The previous receive URL is still in its overlap. Wait for it to end, or rotate with overlap_seconds: 0."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422overlap_seconds is invalid, a field is unknown, or a header is missing or malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`overlap_seconds` must be from 0 to 86400."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
POST/webhooks/sources/{id}/rotate_secret
Rotate the verification secret
Replaces the secret with one of the same kind (secret, or secret_base64 for an hmac source). The old one also verifies for overlap_seconds (86,400 by default); 0 retires it at once. Motor never shows a secret back.
Request body{
"secret": "whsec_live_7cTqWm2R2d9",
"overlap_seconds": 86400
}
Responses
200The source.
{
"id": "wds_8kJhGf3dSa7QwErTy5UiOp",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"name": "Stripe live",
"verification": {
"kind": "stripe",
"secret_hint": "R2d9",
"secret_version": 2,
"old_secret_expires_at": "2026-10-10T18:22:03Z"
},
"receive_path": "/webhooks/receive/…x9Qz",
"token_version": 1,
"status": "enabled",
"event_type": "stripe.event",
"event_type_path": "/type",
"event_type_prefix": "stripe.",
"dedup_header": null,
"billing_key_id": "key_BWBeN28Vb7cMEx7Ym8AUzs",
"metadata": {},
"counters": {
"accepted": 0,
"duplicates": 0,
"rejected": 0
},
"version": "2",
"created_at": "2026-10-09T18:22:01Z",
"updated_at": "2026-10-09T18:22:03Z"
}
400The body isn't valid JSON, isn't an object, or has a duplicate key (invalid_json).
{
"error": {
"code": "invalid_json",
"message": "The request body isn't valid JSON. Duplicate key \"name\" at byte 31."
}
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The source doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The source doesn't exist."
}
}
409The previous secret is still in its overlap and overlap_seconds isn't 0 (invalid_state). Also idempotency_conflict for a different request with the same Idempotency-Key, and request_in_progress while it's running.
{
"error": {
"code": "invalid_state",
"message": "The previous secret is still in its overlap. Wait for it to end, or rotate with overlap_seconds: 0."
}
}
412If-Match is stale (revision_conflict). The response's ETag is current: read the resource again and retry.
{
"error": {
"code": "revision_conflict",
"message": "The resource changed since you read it. Read it again and retry with its new ETag.",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body isn't application/json (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Send the request body as application/json."
}
}
422The secret doesn't fit the source's kind, a field is invalid or unknown, or a header is missing or malformed (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`secret` must be from 16 to 256 UTF-8 bytes."
}
}
428If-Match is missing (precondition_required).
{
"error": {
"code": "precondition_required",
"message": "Send the ETag of the resource in the If-Match header. Read the resource to get its current ETag."
}
}
429Too many configuration writes: 20 a second, bursts of 40 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503Motor couldn't complete the request, such as sealing a secret or closing a billing month (temporarily_unavailable). Retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "Motor couldn't protect a secret. Retry in a few seconds."
}
}
Receive
POST/webhooks/receive/{token}
Receive a provider's webhook
The public URL that you give a provider. It needs no Motor key: the token selects the source, and the provider's signature (or bearer token) authenticates the request, checked against the exact raw bytes before anything else. A verified, new request becomes a message of the source's tenant, charged to the source's billing key like POST /messages ($0.00001 per 64 KiB ingested, at least 1 unit, even with no matching endpoint, plus $0.00005 per 16 KiB per endpoint delivery), and Motor answers only after it's stored. A duplicate (the same provider ID and bytes, or the same body within 24 hours for providers without signed IDs) gets 200 without a new message or charge. Failed requests are neither kept nor charged.
Acknowledgments: 202 {"message_id", "duplicate": false} for a new request and 200 {"message_id", "duplicate": true} for a duplicate; Slack gets 200 with an empty body, or 200 {"challenge"} for a signed url_verification (admitted as a paid ingestion-only motor.source_verification message); Twilio gets 200 application/xml <Response/>. Motor never passes a receiver's response back. Requests are limited to 50 a second per source, bursts of 100, and 20 failed verifications a second per IP address.
This endpoint authenticates with the secret in its URL. Keep the URL private.
Request body{
"id": "evt_1Q2w3E4r",
"type": "invoice.paid",
"livemode": true,
"data": {
"object": {
"id": "in_1Q2w"
}
}
}
Responses
200A duplicate (duplicate: true, no new charge); or any accepted Slack request (an empty body, or {"challenge"} for URL verification); or any accepted Twilio request (<Response/>).
{
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"duplicate": true
}
202Accepted, stored, fanned out, and charged. Delivery happens afterwards.
{
"message_id": "wdm_2xCvBn6mQwE9rTyUi4OpAs",
"duplicate": false
}
400The JSON body is malformed (invalid_json), a form body is malformed (invalid_form), or the request has two Content-Type headers (invalid_request). Only after verification, except the header check.
{
"error": {
"code": "invalid_json",
"message": "The body isn't valid JSON."
}
}
401The signature, timestamp, or bearer secret is missing, duplicated, malformed, stale, or wrong (invalid_signature).
{
"error": {
"code": "invalid_signature",
"message": "The request has no Stripe-Signature header."
}
}
402The account has no payment card or the source's billing key is revoked (billing_required), or the charge would exceed the billing key's spending limit (spending_limit). The provider should retry; Motor records a webhooks.budget.blocked event.
{
"error": {
"code": "spending_limit",
"message": "This work would exceed the paying API key's monthly spending limit. Raise the limit on the API keys page, or wait until the next billing month starts at 00:00 UTC."
}
}
404No source has this token, or its tenant is deleted (not_found).
{
"error": {
"code": "not_found",
"message": "No source has this receive URL."
}
}
409A request with the same provider ID and different bytes was already accepted (invalid_state).
{
"error": {
"code": "invalid_state",
"message": "A request with this event ID and different bytes was already accepted."
}
}
413The body is larger than 1 MiB (body_too_large).
{
"error": {
"code": "body_too_large",
"message": "The request body must be at most 1048576 bytes."
}
}
415The body is compressed, or its type isn't JSON, form, text, or octet-stream (Twilio: form or JSON only) (unsupported_media_type).
{
"error": {
"code": "unsupported_media_type",
"message": "Sources don't accept compressed bodies. Send them without Content-Encoding."
}
}
422The event type that the request maps to isn't active (invalid_event_type), or a provider field is missing, such as Stripe's id, type, and livemode or GitHub's delivery ID (invalid_field). Register the type, then let the provider retry.
{
"error": {
"code": "invalid_event_type",
"message": "The event type stripe.charge.refunded isn't an active type of your catalog. Create it with POST /webhooks/event_types first.",
"field": "event_type"
}
}
429Too many requests for the source, too many failed verifications from the address, or too many admissions for the team (rate_limited); or the team's pending deliveries or stored content is at its limit (queue_full).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
503The source is paused, the account can't accept webhooks now, or Motor couldn't store the request (temporarily_unavailable). The provider should retry.
{
"error": {
"code": "temporarily_unavailable",
"message": "The source is paused. Retry later."
}
}
Analytics and events
Get analytics
Returns hourly or daily counts of admissions, deliveries, attempts, and outcomes in a window of at most 30 days, and the current queue. Counts are by the time of each transition, eventually consistent within about a minute, and never a billing record. Without filters they cover the whole team; endpoint_id, source_id, and tenant_id narrow them (in that order of precedence).
Responses
200The counts.
{
"bucket": "hour",
"created_after": "2026-10-09T00:00:00Z",
"created_before": "2026-10-09T03:00:00Z",
"data": [
{
"start": "2026-10-09T00:00:00Z",
"admitted_messages": 1200,
"ingest_bytes": 1310720,
"deliveries": 2400,
"attempts": 2461,
"succeeded": 2380,
"failed": 3,
"expired": 0,
"canceled": 2,
"duplicates": 14,
"rejected_auth": 1,
"throttled": 0,
"queue_depth": 0,
"oldest_due_age_seconds": 0
},
{
"start": "2026-10-09T02:00:00Z",
"admitted_messages": 860,
"ingest_bytes": 901120,
"deliveries": 1720,
"attempts": 1733,
"succeeded": 1712,
"failed": 0,
"expired": 0,
"canceled": 0,
"duplicates": 6,
"rejected_auth": 0,
"throttled": 0,
"queue_depth": 0,
"oldest_due_age_seconds": 0
}
],
"queue_depth": 37,
"oldest_due_age_seconds": 4
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422A time is missing or invalid, the window is longer than 30 days, bucket is invalid, or tenant_id, endpoint_id, or source_id doesn't name a resource of your team (invalid_field). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`created_after` and created_before are required, at most 30 days apart."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
List events
Lists operational events of the last 30 days, oldest first: failed and expired deliveries, disabled and recovered endpoints, rejected source requests, completed and failed replays, and captures blocked by spending. Events hold IDs, reasons, counts, and times, never payloads or secrets.
Responses
200A page of events.
{
"data": [
{
"id": "wdv_9oLpKi4JuHy7GtFrDe2SwQ",
"type": "webhooks.endpoint.disabled",
"created_at": "2026-10-09T18:22:01Z",
"mode": "live",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"resource_version": "4",
"data": {
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"reason": "endpoint_gone"
},
"coalesced": false,
"count": 1,
"first_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"last_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
],
"next_cursor": null
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
422type or limit is invalid (invalid_field), or the cursor isn't for this list (invalid_cursor). Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`type` must be one of webhooks.delivery.failed, ..."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}
Get an event
Returns an event of the last 30 days.
Responses
200The event.
{
"id": "wdv_9oLpKi4JuHy7GtFrDe2SwQ",
"type": "webhooks.endpoint.disabled",
"created_at": "2026-10-09T18:22:01Z",
"mode": "live",
"resource_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"tenant_id": "wdt_4fRm8NqZ2kLpVx7cJs9hYb",
"resource_version": "4",
"data": {
"endpoint_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"reason": "endpoint_gone"
},
"coalesced": false,
"count": 1,
"first_id": "wde_6mNbVc8xZaS2dFgH4jKlQw",
"last_id": "wde_6mNbVc8xZaS2dFgH4jKlQw"
}
401The key is missing, invalid, revoked, publishable, or not allowed to use webhooks (unauthorized).
{
"error": {
"code": "unauthorized",
"message": "This API key is invalid or has been revoked."
}
}
403The team's account, or its use of webhooks, is suspended (account_suspended).
{
"error": {
"code": "account_suspended",
"message": "This team's Motor account is suspended. Contact support@motor.dev for help."
}
}
404The event doesn't exist, or belongs to another team (not_found).
{
"error": {
"code": "not_found",
"message": "The event doesn't exist."
}
}
422 Also unsupported_mode / invalid_field for Motor-Mode.
{
"error": {
"code": "invalid_field",
"message": "`Motor-Mode` must be live or test."
}
}
429Too many reads: 100 a second, bursts of 200 (rate_limited).
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Retry after 1 seconds."
}
}