Skip to main content
BETA — SUBJECT TO CHANGE. This API is in beta and may change without notice.
KYC outcomes that resolve after the initial request — instant approvals (whose account provisioning completes asynchronously), DocV results (DocV coming soon), and manual-review decisions — are delivered to a webhook URL you register. Webhooks are how you learn a participant’s final provisioned provisioned_participant and any rejection.
Only terminal outcomes are delivered. Intermediate states (DocV in progress, manual review) produce no webhook — poll GET /v1/kyc/status if you need to track them.

Register your webhook

POST /v1/kyc/webhook — requires the write:kyc scope. Register once per firm (not per participant). Registration runs a test POST to your URL before saving; if the test fails, nothing is saved and you may retry.
Also available over gRPC as polymarket.us.kyc.v1.KYCAPI/SetWebhookURL with the same two fields and the same validation behavior — see the transport mapping.

Request fields

You bring your own signing secret. Polymarket US never generates or returns a secret — you supply and keep it. Use the Standard Webhooks whsec_<base64-key> format (e.g. whsec_ followed by the output of openssl rand -base64 32). Registration is last-writer-wins: re-registering without a signing_secret clears any previously stored one (switching you to unsigned).

Response

Test-POST contract

Registration sends a POST to your URL with a normal notification envelope whose event_type is webhook.test and whose data is a fixed informational message. It is signed (same scheme as real deliveries) when you supplied a signing_secret. Your endpoint passes only by returning a 2xx. Redirects are not followed, and URLs that resolve to private or internal IP ranges are refused.

Receiving notifications

Polymarket US sends an HTTP POST with a JSON body to your registered URL.

Headers

Signed deliveries follow the Standard Webhooks convention. The three signing headers are present only when a signing secret is configured.

Body

Webhook data fields are snake_case (e.g. external_id, provisioned_participant), unlike the camelCase REST responses. See Field naming.

Event types

data fields

Empty fields are omitted from the JSON.

Using these identifiers to trade

When you place an order on behalf of a participant, provisioned_participant is the “who” — pass it as the x-participant-id header on participant-scoped requests (trading, positions, reports). It is the only provisioning identifier you need.
Use provisioned_participant, not external_id / user_id, to identify the participant on a trading call. The external_id (and its legacy duplicate user_id) is the identifier you supplied — it is meaningful only inside your own systems. The exchange identifies the participant by the provisioned_participant value carried in the x-participant-id header.
The webhook and GET /v1/kyc/status carry the same value under different field names — the webhook calls it provisioned_participant, while the status read calls it participantId.

Delivery semantics

  • At-least-once. Deduplicate on event_id (the webhook-id header) — you may receive the same event more than once.
  • Retries. Only a 2xx counts as delivered. Redirects are not followed (a 3xx is a failed delivery); every non-2xx or transport error is retried with jittered exponential backoff up to a maximum attempt count. Retry-After is honored on 408 / 429. A repeatedly-failing endpoint is circuit-broken: deliveries pause for a cooldown that grows with each consecutive re-open, then resume.
  • Respond fast. Return 2xx promptly once you’ve durably accepted the event; do heavy processing asynchronously.

Verifying the signature

Because the secret is in the standard whsec_/base64 format, a Standard Webhooks SDK verifies deliveries out of the box — initialise it with the default constructor (e.g. NewWebhook in Go), passing the secret exactly as you registered it. No raw-key escape hatch (NewWebhookRaw) is needed. If you verify inline instead, the signed content is <webhook-id>.<webhook-timestamp>.<raw body> — the raw request bytes, before any JSON re-serialization. The HMAC key is the decoded Standard Webhooks key: strip the optional whsec_ prefix from your signing_secret and base64-decode the remainder using standard padded base64. Compute the expected signature, then constant-time compare it against each space-delimited candidate in webhook-signature; accept if any matches.
Reject deliveries whose webhook-timestamp is too old to limit replay exposure, and always verify against the raw body you received rather than a re-encoded copy.

Next steps

Verification Flow

The synchronous outcomes that precede these webhooks.

Overview

How the KYC process fits together.