Webhook endpoints
Marea POSTs JSON events to webhook endpoints you register. An endpoint is a first-class object — it has its own URL, its own signing secret, its own set of subscribed events, and its own delivery log. You can register up to 16 endpoints per account (e.g. one for production, one for staging, one for an internal audit log). Stripe-style model: events fan out to every enabled endpoint whosesubscribedEvents list contains the event type. Adding a new destination is one POST; you do not need a new developer key.
Available events
Where order events come from. A single developer endpoint receives
order.* events for every storefront your key bootstrapped — you do not register one endpoint per merchant. If you revoke the key that bootstrapped a given user, their order events stop flowing to your endpoint automatically.Create an endpoint
201 Created):
developer:webhooks (added to new keys automatically) or developer:bootstrap (existing keys keep working).
List, update, delete
Pause an endpoint
To stop deliveries without losing the URL, signing secret, or subscribed events, setenabled: false. Marea filters disabled endpoints out of fan-out before any HTTP attempt — zero retries, zero delivery rows. Flip it back when your receiver is ready:
/developers/webhooks (each row has a Pause / Resume button). Use it during deploys, receiver outages, or signing-secret swaps.
URL validation
URLs are rejected at registration time when:- the scheme is not
https:(HTTP is never accepted) - the length exceeds 2048 characters
- the hostname is
localhost,0.0.0.0,metadata.google.internal, anywhere in127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16, IPv6 ULA (fc00::/7), IPv6 link-local (fe80::/10), or any hostname ending in.internal/.local
Event envelope
Every webhook is aPOST with Content-Type: application/json. The body is the same envelope shape across all event types:
eventId is a UUID v4 — use it as the idempotency key on your receiver. Marea may retry; processing the same eventId more than once must be a no-op on your side.
Payload data shapes
user.verified
user.cancelled
order.created / order.status_updated / order.paid
order.created carries the full order payload (items, pricing, customer, delivery, payment method):
order.status_updated is a slim payload (previousState → newState only) — receivers cache order.created and merge by orderId. order.paid is shaped like order.created plus data.paymentMethod.id and data.paidAt. Test fires from the dashboard add __test: true to the envelope.
Headers Marea sends
Verify signatures
Every webhook is signed with HMAC-SHA256. The signed string is"<timestamp>.<rawBody>"; the result is hex-encoded in the v1= field.
- Parse
t=andv1=fromX-Marea-Signature - Reject if
|now - t| > 300(5-minute replay window) - Compute
HMAC-SHA256(signingSecret, "<t>.<rawBody>")and hex-encode - Compare against
v1using a constant-time function - On match: trust the payload. On mismatch: respond
401.
signingSecret is the raw 64-char hex string returned at create/rotate time — no further derivation needed.
Ready-to-paste verifiers in JavaScript and Python live in Webhook receiver helpers.
Rotate the signing secret
signingSecretVersion bumps by 1; the old secret stops working immediately. Coordinate the rotation with your receiver — there is no overlap window in v1.
Retry behavior
Marea expects a2xx response from your endpoint within 5 seconds. Anything else (non-2xx, timeout, connection error) triggers the retry path:
After attempt 3 fails, the delivery row is marked
max_attempts_reached. For user.* events specifically, we also send a delivery-failure email to the developer who owns the endpoint so a misconfigured receiver doesn’t silently lose lifecycle events.
You can inspect recent deliveries in the dashboard at /developers/webhooks/{endpointId} (last 50 per endpoint, 30-day retention).
There is no automatic replay endpoint. If you need bulletproof delivery, queue events on your end so a transient receiver outage doesn’t lose them.
Receiver implementation tips
- Respond 200 immediately, then process asynchronously. Marea’s 5-second timeout is tight.
- Idempotency: use
eventIdfrom the envelope as your dedupe key. Retries are rare but possible. - Constant-time signature comparison: use
crypto.timingSafeEqual(Node) orhmac.compare_digest(Python). Never===on HMAC. - Multiplex one server across multiple endpoints: the
X-Marea-Endpoint-Idheader tells you which endpoint a delivery came from. - Test with
webhook.sitewhile wiring up: register a test endpoint pointed at your uniquewebhook.siteURL, fire test events from the dashboard, then move the URL to your real receiver.
Limitations and roadmap
- Hard cap of 16 endpoints per account. We’ll raise it if real-world usage justifies.
- No overlap window on secret rotation. To roll a secret without dropping events, create a second endpoint with the same
subscribedEvents, point your receiver at both for a brief cutover, then pause and delete the old one. - No event-replay endpoint. Once the 3 retries fail, the event is gone — queue events on your side if you need bulletproof delivery.