Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /go-webhook-event-types.md.
Verified · 8/11/2026

Webhook Event Types

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.

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.

Why the header, not the body#

The stored body is sent verbatim with no shared envelope, so its shape depends on which family it belongs to:

FamilyExample typeBody shape
email.*email.receivedCarries the event name in a top-level event field
payment.*payment.settledFlat fields; the event name is in type, not event
interaction.*interaction.x402.challengeJust {"interaction": {...}}, no event or type field at all

Because 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.

The event catalog#

The full set of current header values is exported as the WebhookEventTypes slice:

Email family (subject: an email)

  • email.received
  • email.bounced
  • email.tls_report
  • email.dmarc_report
  • email.dmarc_failure

Payment family (subject: an x402 settlement)

  • payment.settled
  • payment.failed

Interaction family (subject: an x402-over-email or ack interaction step)

  • interaction.x402.challenge
  • interaction.x402.payment
  • interaction.x402.settled
  • interaction.x402.rejected
  • interaction.x402.declined
  • interaction.x402.expired
  • interaction.x402.verify_timeout
  • interaction.ack.received
  • interaction.ack.requested
  • interaction.ack.acked
  • interaction.ack.canceled
  • interaction.ack.expired

Only 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.

Verifying and dispatching a delivery#

Signature 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.

HandleWebhookEvent does the verify-then-classify sequence in one call:

package main

import (
	"io"
	"log"
	"net/http"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

func handler(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	event, err := primitive.HandleWebhookEvent(primitive.HandleWebhookOptions{
		Body:    body,
		Headers: r.Header,
		Secret:  "whsec_...",
	})
	if err != nil {
		// signature or verification failure
		http.Error(w, "invalid webhook", http.StatusBadRequest)
		return
	}

	switch {
	case primitive.IsPaymentSettledEvent(event):
		settled := event.(primitive.PaymentEvent) // flat fields; amount in base units
		log.Println("settled:", settled.ChallengeID, settled.Amount, settled.SettleTx)
	case primitive.IsInteractionX402Event(event):
		x402 := event.(primitive.InteractionEvent) // interaction.x402.* lifecycle
		log.Println("interaction:", x402.Event)
	default:
		if received, ok := event.(primitive.EmailReceivedEvent); ok {
			log.Println("inbound email:", received.Email.Headers.Subject)
		}
	}

	w.WriteHeader(http.StatusOK)
}

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.

HandleWebhookEvent vs. the legacy HandleWebhook#

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 for the shared contract this catalog implements.

Lower-level helpers#

For 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.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer