{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/go-webhook-event-types","markdown_url":"https://test.abhinandan.one/go-webhook-event-types.md","article":{"id":"cbc2059e-4377-4733-91b0-4f98db83ac7f","article_slug":"go-webhook-event-types","parent_article_slug":null,"parent_article_title":null,"kind":"concept","published_at":"2026-08-11T18:54:54.961778+00:00","keywords":["X-Webhook-Event header","WebhookEventTypes","HandleWebhookEvent","PaymentEvent Go","InteractionEvent Go","IsPaymentSettledEvent"],"meta_description":"The X-Webhook-Event HTTP header names every Primitive webhook delivery's event type across the email.*, payment.*, and interaction.* families.","og_image_url":null,"source_file_paths":["sdk-go/webhook.go"],"recording_id":null,"replayable":false,"task_name":"Webhook Event Types","category":"Go SDK","summary":null,"description":"Explains the Go SDK's webhook event catalog, email, payment, and interaction families, and how HandleWebhookEvent uses the X-Webhook-Event header to return typed events with forward compatibility for unknown types.","content_kind":"repo_page","content_markdown":"Every Primitive webhook delivery carries its event type in the **`X-Webhook-Event`** HTTP header, not in the response body. The Go SDK's `primitive.HandleWebhookEvent` reads that header to return a typed Go value for known event types, and a forward-compatible `UnknownEvent` for anything it doesn't recognize yet.\n\n## Why the header, not the body\n\nThe stored body is sent verbatim with no shared envelope, so its shape depends on which family it belongs to:\n\n| Family | Example type | Body shape |\n| --- | --- | --- |\n| `email.*` | `email.received` | Carries the event name in a top-level `event` field |\n| `payment.*` | `payment.settled` | Flat fields; the event name is in `type`, not `event` |\n| `interaction.*` | `interaction.x402.challenge` | Just `{\"interaction\": {...}}`, no `event` or `type` field at all |\n\nBecause only `email.*` bodies self-describe consistently, the header is the one discriminator that works across every family. `ParseWebhookEvent` and `HandleWebhookEvent` both key off it first, falling back to a body-level `event` string only for backward compatibility with older senders.\n\n## The event catalog\n\nThe full set of current header values is exported as the `WebhookEventTypes` slice:\n\n**Email family** (subject: an email)\n\n- `email.received`\n- `email.bounced`\n- `email.tls_report`\n- `email.dmarc_report`\n- `email.dmarc_failure`\n\n**Payment family** (subject: an x402 settlement)\n\n- `payment.settled`\n- `payment.failed`\n\n**Interaction family** (subject: an x402-over-email or ack interaction step)\n\n- `interaction.x402.challenge`\n- `interaction.x402.payment`\n- `interaction.x402.settled`\n- `interaction.x402.rejected`\n- `interaction.x402.declined`\n- `interaction.x402.expired`\n- `interaction.x402.verify_timeout`\n- `interaction.ack.received`\n- `interaction.ack.requested`\n- `interaction.ack.acked`\n- `interaction.ack.canceled`\n- `interaction.ack.expired`\n\nOnly `email.received` carries a dedicated, schema-validated struct (`EmailReceivedEvent`). The other `email.*` types (bounces, TLS/DMARC reports) fall through to `UnknownEvent` today, alongside any type Primitive adds in the future.\n\n## Verifying and dispatching a delivery\n\nSignature verification runs on the raw request body and is independent of which family the delivery belongs to. It works identically for `email.*`, `payment.*`, and `interaction.*` bodies. Each delivery is signed once, and that signature is sent on three headers: `Primitive-Signature` (primary), `X-Primitive-Signature`, and the legacy `X-Webhook-Signature`.\n\n`HandleWebhookEvent` does the verify-then-classify sequence in one call:\n\n```go\npackage main\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\thttp.Error(w, \"bad request\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tevent, err := primitive.HandleWebhookEvent(primitive.HandleWebhookOptions{\n\t\tBody:    body,\n\t\tHeaders: r.Header,\n\t\tSecret:  \"whsec_...\",\n\t})\n\tif err != nil {\n\t\t// signature or verification failure\n\t\thttp.Error(w, \"invalid webhook\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tswitch {\n\tcase primitive.IsPaymentSettledEvent(event):\n\t\tsettled := event.(primitive.PaymentEvent) // flat fields; amount in base units\n\t\tlog.Println(\"settled:\", settled.ChallengeID, settled.Amount, settled.SettleTx)\n\tcase primitive.IsInteractionX402Event(event):\n\t\tx402 := event.(primitive.InteractionEvent) // interaction.x402.* lifecycle\n\t\tlog.Println(\"interaction:\", x402.Event)\n\tdefault:\n\t\tif received, ok := event.(primitive.EmailReceivedEvent); ok {\n\t\t\tlog.Println(\"inbound email:\", received.Email.Headers.Subject)\n\t\t}\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n}\n```\n\n`HandleWebhookEvent` never errors on an unrecognized event type. It returns `UnknownEvent` instead, so a code path that hasn't been updated for a new event type keeps compiling and running rather than breaking on delivery.\n\n## HandleWebhookEvent vs. the legacy HandleWebhook\n\n`HandleWebhook` is hard-typed to `email.received` and returns an error for any other event type, so use `HandleWebhookEvent` unless your integration only ever handles inbound email. `HandleWebhook` remains for backward compatibility with integrations written before the payment and interaction families existed. See [Webhook Events Overview](webhook-events) for the shared contract this catalog implements.\n\n## Lower-level helpers\n\nFor cases where you already have the parsed body and just need to classify it, `ParseWebhookEvent` accepts the raw parsed value plus the `X-Webhook-Event` header value as an optional second argument; this is what `HandleWebhookEvent` calls internally after verification. `VerifyWebhookSignature` is available on its own when you need to verify without parsing at all.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Receiving and Verifying Webhooks\" href=\"go-receiving-webhooks\">\n\nNormalize a verified email.received delivery into a ReceivedEmail with primitive.Receive.\n\n</Card>\n\n<Card title=\"Webhook Payload Schema Validation\" href=\"go-webhook-schema-validation\">\n\nValidate a raw email.received payload against the embedded JSON Schema directly.\n\n</Card>\n\n<Card title=\"x402 Errors\" href=\"go-x402-errors\">\n\nInterpret X402Error status codes and retry-after headers on payment calls.\n\n</Card>\n\n<Card title=\"Webhook Events Overview\" href=\"webhook-events\">\n\nSee the cross-SDK signature verification and event-catalog contract this page implements.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-go/webhook.go","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Webhook+Event+Types&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fgo-webhook-event-types","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}