Webhooks
anymrkt sends signed events to an HTTPS endpoint you choose — the same model as Stripe's event destinations. Create destinations in Integration Studio → Event destinations, or with the studio_create_event_destination MCP tool.
Destinations are scoped
Each destination receives events from exactly one place, chosen when it is created and never widened afterwards:
- One brand — orders, refunds, disputes, publishes for that brand only.
- One board — card, comment, sprint, epic and connector changes on that Kanban board.
- Your account — agents connecting, refused or rate-limited calls, usage thresholds, keys, tokens, plan changes. Only you can create these, signed in; an agent token cannot.
Events are named one by one — there are no wildcards — so a new event type never starts arriving at your endpoint until you add it. An agent token may create a brand destination only for a brand it was given, and only with thewebhooks permission.
Payload: thin or snapshot
Thin (the default) says what happened and where to look. Snapshot adds a data object with the record as it was, secrets redacted. Every payload carries contextnaming the scope it came from, and the destination's pinned api_version.
{
"id": "evt_9f2c…",
"object": "event",
"type": "order.paid",
"api_version": "2026-10-03",
"created": 1759536000,
"livemode": true,
"context": { "brand_id": "b1c2…" },
"related_object": { "id": "ord_…", "type": "order", "url": "/sell/orders/ord_…" }
}Verifying the signature
Every request has Anymrkt-Signature: t=<unix>,v1=<hex>, where the hex is HMAC-SHA256 of "<t>.<raw body>" with your signing secret. Reject requests older than five minutes. After you roll a secret, the old one keeps signing for 24 hours alongside the new one, so there are two v1 values.
import crypto from 'node:crypto'
// Use the RAW request body — re-serialised JSON will not match the signature.
export function verifyAnymrkt(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
header.split(',').map((p) => p.split('=')).filter(([k]) => k === 't').map(([k, v]) => [k, v]),
)
const t = Number(parts.t)
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
// During a secret roll there are two v1 values; either may match.
return header.split(',').filter((p) => p.startsWith('v1=')).some((p) => {
const got = Buffer.from(p.slice(3), 'hex'), want = Buffer.from(expected, 'hex')
return got.length === want.length && crypto.timingSafeEqual(got, want)
})
}import hmac, hashlib, time
def verify_anymrkt(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
items = [p.split("=", 1) for p in header.split(",")]
t = int(next(v for k, v in items if k == "t"))
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(v, expected) for k, v in items if k == "v1")Also sent: Anymrkt-Event-Id (use it to ignore duplicates) and Anymrkt-Event-Type.
Delivery and retries
- Answer with any 2xx within 10 seconds. Redirects are not followed.
- Failures are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 24 hours, then abandoned. You can resend any delivery from the log.
- A destination that has failed continuously for 3 days is disabled; re-enable it in the Studio once fixed.
- Deliveries can arrive more than once and out of order — key on the event id.
- Endpoints must be public HTTPS addresses; private and internal addresses are refused.
No public URL? Pull instead
Agents usually cannot receive webhooks. The events_list MCP tool returns the same events, oldest first after a cursor — call it with since_cursor set to the last next_cursor you saw. events_get returns one event in full.
Event types
From your account
agent.connected— A new MCP client connected with one of your tokens.agent.limit_reached— An agent was refused because your plan’s concurrent-agent limit is in use.tool_call.denied— An agent’s tool call was refused (permission, scope, or confirm required).tool_call.limited— An agent’s tool call was refused by a usage or rate limit.usage.threshold_reached— A tool reached 80% of this month’s allowance.usage.limit_reached— A tool reached 100% of this month’s allowance.mcp_token.created— An MCP token was issued.mcp_token.revoked— An MCP token was revoked.api_key.created— A Studio API key was created.api_key.revoked— A Studio API key was revoked.plan.changed— Your subscription plan changed (upgrade, downgrade, or lapse).
From a brand
order.paid— A buyer paid for an order.order.refunded— An order was refunded, fully or partly.order.disputed— A chargeback was opened or changed status.listing.published— A listing went live.page.published— The brand’s Builder page was published.
From a board
task.created— A card was created on the board.task.status_changed— A card moved: claimed, completed, blocked, reopened…task.comment_added— A comment was added to a card.sprint.status_changed— A sprint started, completed, or was cancelled.epic.status_changed— An epic changed status.connector.created— A connector was added to the board.connector.updated— A connector was changed or toggled.connector.deleted— A connector was removed.