Verify that webhook deliveries are authentic and untampered
Every webhook delivery is signed with the endpoint’s signing secret using HMAC-SHA256. Verify the signature before processing any event.parseWebhookEvent (sync, node:crypto) and parseWebhookEventAsync (WebCrypto) throw WebhookVerificationError when the signature is invalid, the timestamp is stale (older than 5 minutes by default), or a required header is missing. Both return a typed WebhookEvent on success. Next.js app router uses the async function.
timestamp is the Webhook-Timestamp header (Unix seconds)
body is the raw request body (not parsed JSON)
The result is base64-encoded and prefixed with v1,. The Webhook-Signature header may contain multiple space-separated signatures during key rotation. Accept any match.For a verifier in another language, remove the whsec_ prefix from the signing secret and
base64-decode the remainder to obtain the HMAC key. Compare signatures in constant time, and
reject timestamps more than five minutes in the past or future. The SDK handles these checks.
Always read the raw body for signature verification. Parsing JSON first and re-stringifying
changes whitespace and breaks the HMAC.
Use parseWebhookEvent where node:crypto exists, and parseWebhookEventAsync for WebCrypto.
Next.js pages-router handlers need export const config = { api: { bodyParser: false } }.
In Express, mount express.raw({ type: "application/json" }) on the webhook route before
any JSON middleware. Both must pass the original body bytes to the verifier.The handler above illustrates signature verification. For production processing, add durable
deduplication as described below before running your event handlers.
Deduplicate on Webhook-Id, which stays the same across retries. Concurrent deliveries can
overlap, so a separate check, process, and mark sequence is not sufficient.One option is a durable inbox in your own database. Create a unique key on the delivery ID:
CREATE TABLE agg_webhook_inbox ( id text PRIMARY KEY, payload jsonb NOT NULL, received_at timestamptz NOT NULL DEFAULT now(), processed_at timestamptz);
After verification, insert the event atomically. Here db is your server’s database client
with parameterized queries. Return 2xx only after the insert commits; database errors must
return a failure so AGG can retry.
Your worker processes unprocessed rows and commits its database changes and processed_at
together in one transaction. Use the delivery ID as an idempotency key for external side
effects too. Keep processed IDs for your replay window, not just the automatic retry period;
the replay endpoint can resend older deliveries.Events can arrive out of order. Use the event’s resource IDs to read current API state before
updating a balance or order display. See delivery behavior.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Agg.market — Prediction Market Aggregator","description":"White-label prediction market aggregator API: sign users in, quote the best price across venues, execute trades, and track them to a final status. Start with Quickstart: REST.","colors":{"primary":"#ED8200","light":"#FFA366","dark":"#C66F00"},"logo":{"light":"https://mintcdn.com/agg/SLxnoWBYT8Zyhg9W/assets/agg-dark.svg?fit=max\u0026auto=format\u0026n=SLxnoWBYT8Zyhg9W\u0026q=85\u0026s=f1b6e05f3e2529bc2c7698267234f5d1","dark":"https://mintcdn.com/agg/SLxnoWBYT8Zyhg9W/assets/agg-white.svg?fit=max\u0026auto=format\u0026n=SLxnoWBYT8Zyhg9W\u0026q=85\u0026s=4a896195354d17f32101e0ec90fb822c"},"favicon":"https://mintcdn.com/agg/OOif8Fd2iWonTlgU/assets/favicon.svg?fit=max\u0026auto=format\u0026n=OOif8Fd2iWonTlgU\u0026q=85\u0026s=6907ac01c9b9a95394f668f53099f108","background":{"decoration":"grid"},"navbar":{"links":[{"href":"https://storybook.agg.market","label":"Storybook"}],"primary":{"type":"button","label":"Admin Dashboard","href":"https://admin.agg.market"}},"footer":{"socials":{"x":"https://x.com/Snag_Solutions","linkedin":"https://www.linkedin.com/company/snag-solutions"}},"banner":{"content":"AGG is currently in **alpha**. APIs, components, and docs may change.","dismissible":false},"metadata":{"timestamp":false},"markdown":{"instructions":["To integrate AGG, start with Quickstart: REST (https://docs.agg.market/quickstart/rest), then Order lifecycle \u0026 statuses (https://docs.agg.market/concepts/order-lifecycle).","Track every trade until it reaches a terminal status. Before retrying a failed or timed-out call, read Errors, retries \u0026 idempotency (https://docs.agg.market/concepts/errors).","The API reference is generated from https://docs.agg.market/openapi/openapi.json."]},"styling":{"latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"agg","hasAudioNarration":false,"actualSubdomain":"agg","customDomains":["docs.agg.market"],"gitSource":{"type":"github","owner":"snag-solutions","repo":"agg","deployBranch":"main","contentDirectory":"apps/docs","isPrivate":true,"mountPath":"","label":""},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6aeda357ea85807f25ff5717","clientVersion":"0.0.4087","searchFilterCounts":[{"language":"default","tag":"Trading API","count":80},{"language":"default","tag":"Guides","count":72},{"language":"default","tag":"Trading","count":22},{"language":"default","tag":"Server API","count":21},{"language":"default","tag":"Users","count":13},{"language":"default","tag":"Core concepts","count":12},{"language":"default","tag":"Funding","count":12},{"language":"default","tag":"Market data","count":11},{"language":"default","tag":"Markets","count":10},{"language":"default","tag":"Partner Admin","count":10}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"recipes/webhooks/verifying-signatures","slug":"/recipes/webhooks/verifying-signatures","pageMetadata":{"title":"Verifying Webhook Signatures","description":"Verify that webhook deliveries are authentic and untampered","href":"/recipes/webhooks/verifying-signatures"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Verify that webhook deliveries are authentic and untampered\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`Every webhook delivery is signed with the endpoint’s signing secret using HMAC-SHA256. Verify the signature before processing any event.`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`parseWebhookEvent`}),` (sync, `,_jsx(_components.code,{children:`node:crypto`}),`) and `,_jsx(_components.code,{children:`parseWebhookEventAsync`}),` (WebCrypto) throw `,_jsx(_components.code,{children:`WebhookVerificationError`}),` when the signature is invalid, the timestamp is stale (older than 5 minutes by default), or a required header is missing. Both return a typed `,_jsx(_components.code,{children:`WebhookEvent`}),` on success. Next.js app router uses the async function.`]}),`\n`,_jsx(Heading,{level:`2`,id:`the-signature-scheme`,children:`The signature scheme`}),`\n`,_jsx(_components.p,{children:`The HMAC is computed over:`}),`\n`,_jsx(CodeBlock,{numberOfLines:`2`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`2`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`{webhook_id}.{timestamp}.{body}`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`webhook_id`}),` is the `,_jsx(_components.code,{children:`Webhook-Id`}),` header`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`timestamp`}),` is the `,_jsx(_components.code,{children:`Webhook-Timestamp`}),` header (Unix seconds)`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`body`}),` is the raw request body (not parsed JSON)`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`The result is base64-encoded and prefixed with `,_jsx(_components.code,{children:`v1,`}),`. The `,_jsx(_components.code,{children:`Webhook-Signature`}),` header may contain multiple space-separated signatures during key rotation. Accept any match.`]}),`\n`,_jsxs(_components.p,{children:[`For a verifier in another language, remove the `,_jsx(_components.code,{children:`whsec_`}),` prefix from the signing secret and\nbase64-decode the remainder to obtain the HMAC key. Compare signatures in constant time, and\nreject timestamps more than five minutes in the past `,_jsx(_components.strong,{children:`or future`}),`. The SDK handles these checks.`]}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`Always read the raw body for signature verification. Parsing JSON first and re-stringifying\nchanges whitespace and breaks the HMAC.`})}),`\n`,_jsx(Heading,{level:`2`,id:`next-js-app-router`,children:`Next.js (app router)`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`27`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`27`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`// app/api/webhooks/agg/route.ts`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`parseWebhookEventAsync`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`WebhookVerificationError`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"@agg-build/sdk/server\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`export`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` async`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` function`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` POST`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`req`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`:`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#4EC9B0`},children:` Request`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` try`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` event`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` parseWebhookEventAsync`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`text`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` Object`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`fromEntries`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`headers`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` process`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`env`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:`AGG_WEBHOOK_SECRET`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`!`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` );`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` switch`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`event`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`type`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` case`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"trades.placed\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` handleTrade`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`event`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`data`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` break`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` case`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"markets.resolved\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` handleResolution`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`event`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`data`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` break`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` Response`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`json`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`({ `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`received:`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` });`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` } `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`catch`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`err`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` if`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`err`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` instanceof`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#4EC9B0`},children:` WebhookVerificationError`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` Response`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`json`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`({ `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`error:`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` err`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`message`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }, { `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`status:`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 401`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` });`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` throw`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` err`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Use `,_jsx(_components.code,{children:`parseWebhookEvent`}),` where `,_jsx(_components.code,{children:`node:crypto`}),` exists, and `,_jsx(_components.code,{children:`parseWebhookEventAsync`}),` for WebCrypto.\nNext.js pages-router handlers need `,_jsx(_components.code,{children:`export const config = { api: { bodyParser: false } }`}),`.\nIn Express, mount `,_jsx(_components.code,{children:`express.raw({ type: \"application/json\" })`}),` on the webhook route `,_jsx(_components.strong,{children:`before`}),`\nany JSON middleware. Both must pass the original body bytes to the verifier.`]}),`\n`,_jsx(_components.p,{children:`The handler above illustrates signature verification. For production processing, add durable\ndeduplication as described below before running your event handlers.`}),`\n`,_jsx(Heading,{level:`2`,id:`idempotency`,children:`Idempotency`}),`\n`,_jsxs(_components.p,{children:[`Deduplicate on `,_jsx(_components.code,{children:`Webhook-Id`}),`, which stays the same across retries. Concurrent deliveries can\noverlap, so a separate check, process, and mark sequence is not sufficient.`]}),`\n`,_jsx(_components.p,{children:`One option is a durable inbox in your own database. Create a unique key on the delivery ID:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`sql`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`sql`,children:_jsxs(_components.code,{language:`sql`,numberOfLines:`6`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`CREATE`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` TABLE`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` agg_webhook_inbox`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` id `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`text`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` PRIMARY KEY`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` payload jsonb `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`NOT NULL`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` received_at `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`timestamptz`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` NOT NULL`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` DEFAULT`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` now`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` processed_at `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`timestamptz`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`After verification, insert the event atomically. Here `,_jsx(_components.code,{children:`db`}),` is your server’s database client\nwith parameterized queries. Return 2xx only after the insert commits; database errors must\nreturn a failure so AGG can retry.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`16`,language:`typescript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`typescript`,children:_jsxs(_components.code,{language:`typescript`,numberOfLines:`16`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`async`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` function`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` handle`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`req`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`:`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#4EC9B0`},children:` Request`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` event`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` parseWebhookEventAsync`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`text`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` Object`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`fromEntries`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`headers`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` process`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`env`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:`AGG_WEBHOOK_SECRET`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`!`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` );`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` webhookId`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`headers`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`get`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"webhook-id\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`!`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` await`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` db`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`query`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:\" `INSERT INTO agg_webhook_inbox (id, payload) VALUES ($1, $2)\"})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:\" ON CONFLICT (id) DO NOTHING`\"}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` [`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`webhookId`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:`JSON`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`stringify`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`event`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)],`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` );`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` Response`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`json`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`({ `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`received:`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` true`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` });`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Your worker processes unprocessed rows and commits its database changes and `,_jsx(_components.code,{children:`processed_at`}),`\ntogether in one transaction. Use the delivery ID as an idempotency key for external side\neffects too. Keep processed IDs for your replay window, not just the automatic retry period;\nthe `,_jsx(_components.a,{href:`/recipes/webhooks/overview#rotate-and-replay`,children:`replay endpoint`}),` can resend older deliveries.`]}),`\n`,_jsxs(_components.p,{children:[`Events can arrive out of order. Use the event’s resource IDs to read current API state before\nupdating a balance or order display. See `,_jsx(_components.a,{href:`/recipes/webhooks/overview#delivery-behavior`,children:`delivery behavior`}),`.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Verifying Webhook Signatures","description":"Verify that webhook deliveries are authentic and untampered","href":"/recipes/webhooks/verifying-signatures"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"The signature scheme","slug":"the-signature-scheme","depth":2,"children":[]},{"title":"Next.js (app router)","slug":"next-js-app-router","depth":2,"children":[]},{"title":"Idempotency","slug":"idempotency","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"agg","siteName":"Agg.market — Prediction Market Aggregator","title":"Verifying Webhook Signatures - Agg.market — Prediction Market Aggregator","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-07T12:42:04.949Z","originalFileLocation":"recipes/webhooks/verifying-signatures.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Verify that webhook deliveries are authentic and untampered\">\u003cmeta data-mint-head name=\"application-name\" content=\"Agg.market — Prediction Market Aggregator\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Verifying Webhook Signatures - Agg.market — Prediction Market Aggregator\">\u003cmeta data-mint-head property=\"og:description\" content=\"Verify that webhook deliveries are authentic and untampered\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Agg.market — Prediction Market Aggregator\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.agg.market/recipes/webhooks/verifying-signatures\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://agg.mintlify.site/_mintlify/api/og/v2?division=Webhooks\u0026amp;title=Verifying+Webhook+Signatures\u0026amp;description=Verify+that+webhook+deliveries+are+authentic+and+untampered\u0026amp;theme=ff94093e070c7a0afe40a144\u0026amp;signature=680bc78a99d6635af4a9249a5313c728071be9dcbcbe7cade7047bc7992e629b\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Verifying Webhook Signatures - Agg.market — Prediction Market Aggregator\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Verify that webhook deliveries are authentic and untampered\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://agg.mintlify.site/_mintlify/api/og/v2?division=Webhooks\u0026amp;title=Verifying+Webhook+Signatures\u0026amp;description=Verify+that+webhook+deliveries+are+authentic+and+untampered\u0026amp;theme=ff94093e070c7a0afe40a144\u0026amp;signature=680bc78a99d6635af4a9249a5313c728071be9dcbcbe7cade7047bc7992e629b\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/agg/DToJYvh64DP_aQ9r/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Agg.market — Prediction Market Aggregator\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#ED8200\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.agg.market/recipes/webhooks/verifying-signatures\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/recipes/webhooks/verifying-signatures.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.agg.market/#organization\",\"name\":\"Agg.market — Prediction Market Aggregator\",\"url\":\"https://docs.agg.market\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/agg/SLxnoWBYT8Zyhg9W/assets/agg-dark.svg?fit=max\u0026auto=format\u0026n=SLxnoWBYT8Zyhg9W\u0026q=85\u0026s=f1b6e05f3e2529bc2c7698267234f5d1\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.agg.market#website\",\"name\":\"Agg.market — Prediction Market Aggregator\",\"url\":\"https://docs.agg.market\",\"publisher\":{\"@id\":\"https://docs.agg.market/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures#webpage\",\"url\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures\",\"name\":\"Verifying Webhook Signatures\",\"description\":\"Verify that webhook deliveries are authentic and untampered\",\"dateModified\":\"2026-10-07T12:42:04.949Z\",\"isPartOf\":{\"@id\":\"https://docs.agg.market#website\"},\"breadcrumb\":{\"@id\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Webhooks\",\"item\":\"https://docs.agg.market/recipes/webhooks/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Verifying Webhook Signatures\",\"item\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures#article\",\"headline\":\"Verifying Webhook Signatures\",\"name\":\"Verifying Webhook Signatures\",\"description\":\"Verify that webhook deliveries are authentic and untampered\",\"url\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures\",\"mainEntityOfPage\":{\"@id\":\"https://docs.agg.market/recipes/webhooks/verifying-signatures#webpage\"},\"dateModified\":\"2026-10-07T12:42:04.949Z\",\"publisher\":{\"@id\":\"https://docs.agg.market/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.agg.market#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Agg.market — Prediction Market Aggregator\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Agg.market — Prediction Market Aggregator","nav":{"global":null,"tabs":[{"tab":"Guides","groups":[{"group":"Start here","pages":[{"title":"Overview","href":"/introduction"},{"title":"Quickstart: trading bot / agent","href":"/quickstart/agent"},{"title":"Quickstart: REST","href":"/quickstart/rest"},{"title":"Quickstart: React","href":"/quickstart/react"},{"title":"Choose your integration","href":"/integration-options"},{"title":"Base URL, app IDs \u0026 API keys","href":"/environments"}]},{"group":"Core concepts","pages":[{"title":"Authentication \u0026 sessions","href":"/concepts/authentication"},{"title":"Identifiers","href":"/concepts/ids"},{"title":"Markets, outcomes \u0026 matching","href":"/concepts/markets"},{"title":"Quotes \u0026 smart routing","href":"/concepts/quotes"},{"title":"Execution","href":"/concepts/execution"},{"title":"Order lifecycle \u0026 statuses","href":"/concepts/order-lifecycle"},{"title":"Market resolution \u0026 voids","href":"/concepts/market-resolution"},{"title":"Fees","href":"/concepts/fees"},{"title":"Funding \u0026 withdrawals","href":"/concepts/funding"},{"title":"Accounts \u0026 wallets","href":"/concepts/accounts-and-wallets"},{"title":"Errors, retries \u0026 idempotency","href":"/concepts/errors"},{"title":"Rate limits","href":"/concepts/rate-limits"}]},{"group":"Account","pages":[{"title":"Sign-in providers","href":"/recipes/authentication"},{"title":"Account Linking","href":"/recipes/account-linking"},{"title":"Partner External ID Linking","href":"/recipes/external-id-linking"},{"title":"Avatar Upload","href":"/recipes/avatar-upload"},{"title":"Privy Wallet Provider","href":"/recipes/privy-wallet-provider"},{"title":"KYC","href":"/recipes/kyc"},{"title":"Venue keys","href":"/recipes/venue-keys"}]},{"group":"Trading","pages":[{"title":"Direct Order Execution","href":"/recipes/direct-order-execution"},{"title":"Limit Orders","href":"/recipes/limit-orders"},{"title":"Claim winnings","href":"/recipes/claim-winnings"},{"title":"Self-Custody Trading","href":"/recipes/self-custody"},{"title":"Self-Custody Signing Reference","href":"/recipes/self-custody-signing"},{"title":"Self-Custody Withdrawals","href":"/recipes/self-custody-withdrawals"},{"title":"Market maker trading","href":"/recipes/market-maker"},{"title":"Paper Trading","href":"/recipes/paper-trading"},{"title":"Managed Balance Refills","href":"/recipes/managed-balance-refills"},{"title":"Referrals","href":"/recipes/referrals"},{"title":"Hosted venue accounts","href":"/recipes/hosted-venue-accounts"},{"title":"Onramp","href":"/recipes/onramp"},{"title":"Builder Codes","href":"/recipes/builder-codes"}]},{"group":"Venues","pages":[{"title":"Venues","href":"/venues/overview"},{"title":"Kalshi","href":"/venues/kalshi"}]},{"group":"Market data","pages":[{"title":"Market-data API","href":"/api/market-data"},{"title":"Matched Clusters","href":"/recipes/matched-clusters"},{"title":"aggKey","href":"/recipes/agg-key"},{"title":"Settlement Key Differences","href":"/recipes/settlement-key-differences"},{"title":"Building Market Views","href":"/recipes/building-market-views"},{"title":"Comparing Venue Prices","href":"/recipes/comparing-venue-prices"},{"title":"Building a Sports Market Detail Page","href":"/recipes/sports-market-detail"},{"title":"Hyperliquid Deployers","href":"/recipes/hyperliquid-deployers"},{"title":"Live Crypto Markets","href":"/recipes/live-crypto-markets"},{"title":"News Feed","href":"/recipes/news-feed"},{"title":"Correlated Markets","href":"/recipes/correlated-markets"},{"title":"Venue fees","hidden":true,"href":"/recipes/venue-fees"}]},{"group":"Security","pages":[{"title":"Bot Protection","href":"/recipes/bot-protection"},{"title":"Trusted Proxy","href":"/recipes/partner-trusted-proxy"},{"title":"Venue Geo Availability","href":"/recipes/venue-geo-availability"}]},{"group":"Live orderbooks","pages":[{"title":"Real-Time Orderbook","href":"/recipes/websocket-orderbook"},{"title":"Orderbook streams","href":"/recipes/orderbook-streams"},{"title":"Orderbook firehose","href":"/recipes/orderbook-firehose"}]},{"group":"Real-time","pages":[{"title":"WebSocket Protocol","href":"/api/websocket"},{"title":"Real-Time Charts","href":"/recipes/websocket-charts"},{"title":"Real-Time Arbitrage","href":"/recipes/websocket-arb"},{"title":"User Notifications","href":"/recipes/websocket-notifications"},{"title":"Catalog Sync","href":"/recipes/catalog-sync"}]},{"group":"Webhooks","pages":[{"title":"Webhooks","description":"Receive real-time event notifications at your own HTTPS endpoints","href":"/recipes/webhooks/overview"},{"title":"Verifying Webhook Signatures","href":"/recipes/webhooks/verifying-signatures"},{"title":"Webhook Event Reference","description":"Full payload schemas for every webhook event type","href":"/recipes/webhooks/event-reference"}]},{"group":"Your users","pages":[{"title":"Partner admin","href":"/recipes/partner-admin"},{"title":"Portfolio","href":"/recipes/portfolio"}]},{"group":"UI Kit","pages":[{"title":"Packages","href":"/packages/overview"},{"title":"Components Overview","href":"/components/overview"},{"title":"Customize UI","href":"/components/customization"},{"title":"Leaderboard Page","sidebarTitle":"Leaderboard Page","href":"/components/pages/leaderboard-page"}]},{"group":"Releases","pages":[{"title":"Changelog","href":"/changelog"}]}]},{"tab":"SDK","pages":[{"title":"SDK","href":"/sdk/reference"}]},{"tab":"Trading API","pages":[{"title":"Trading API","href":"/api-reference/authentication/start-authentication"}]},{"tab":"Server API","pages":[{"title":"Server API","href":"/api-reference/webhooks/rotate-webhook-signing-secret"}]},{"tab":"WebSocket API","pages":[{"title":"WebSocket API","href":"/api-reference/websockets/orderbook"}]}]},"basePath":"","title":"Verifying Webhook Signatures - Agg.market — Prediction Market Aggregator","buildId":"dpl_FWZhTb2vab28gFGFH4ojka6qEznQ","deploymentId":"dpl_FWZhTb2vab28gFGFH4ojka6qEznQ","dynamics":["10e4515597"],"chunks":["BannerMarkdown-C79IUEr8.js","dist-DE3EVdvd.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","clsx-DK3K4Z1S.js","html-void-elements-mCSPHezU.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js"]}