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

Payment and Interaction Webhook Event Types

Reference for the WEBHOOK_EVENT_TYPES catalog and the typed PaymentEvent, PaymentSettledEvent, PaymentFailedEvent, and InteractionEvent TypedDicts exported from primitive.events, plus their type-guard functions.

The primitive.events module defines the full catalog of X-Webhook-Event header values and the typed TypedDict shapes for payment.* and interaction.* webhook bodies. For dispatching on these events end to end, see Handling Webhook Events; for the general contract (signature verification, header discriminator, forward compatibility) see Webhook Events Overview.

Every webhook delivery carries its event name in the X-Webhook-Event HEADER, not in the body. The stored body is sent verbatim with no envelope:

  • an email.* body carries event
  • a payment.* body carries the name in type
  • an interaction.* body is just {"interaction": {...}} with no event/type field at all

Because of that, the header is the only reliable discriminator across all three families, which is why handle_webhook_event keys on it.

Event type catalog#

WEBHOOK_EVENT_TYPES is a tuple[str, ...] containing every current catalog value. It is assembled from three grouped tuples, each also exported:

from primitive.events import (
    EMAIL_EVENT_TYPES,
    PAYMENT_EVENT_TYPES,
    INTERACTION_EVENT_TYPES,
    WEBHOOK_EVENT_TYPES,
)
GroupValues
EMAIL_EVENT_TYPESemail.received, email.bounced, email.tls_report, email.dmarc_report, email.dmarc_failure
PAYMENT_EVENT_TYPESpayment.settled, payment.failed
INTERACTION_EVENT_TYPESinteraction.ack.acked, interaction.ack.canceled, interaction.ack.expired, interaction.ack.received, interaction.ack.requested, interaction.x402.challenge, interaction.x402.declined, interaction.x402.expired, interaction.x402.payment, interaction.x402.rejected, interaction.x402.settled, interaction.x402.verify_timeout

WEBHOOK_EVENT_TYPES is the concatenation of all three, in that order. WebhookEventType is a plain str alias for "any current catalog value."

Note

This catalog is a snapshot. New event types can appear on the wire before an SDK release documents them; unrecognized values are handled gracefully rather than rejected (see Webhook Events Overview for the forward-compatibility contract).

is_known_webhook_event_type#

def is_known_webhook_event_type(event_type: str | None) -> bool

Returns True if event_type is a value currently present in WEBHOOK_EVENT_TYPES. Returns False for None and for any value outside the catalog, including future event types your installed SDK version predates.

from primitive.events import is_known_webhook_event_type

is_known_webhook_event_type("payment.settled")       # True
is_known_webhook_event_type("interaction.ack.acked")  # True
is_known_webhook_event_type("payment.refunded")       # False (not yet in the catalog)

PaymentEvent#

A payment.* webhook body. total=False, so every key is optional at the type level; the fields actually present depend on which subtype (PaymentSettledEvent or PaymentFailedEvent) you're looking at.

The stored payload is flat: no envelope, no nested payment object. It carries the event name in type, and the SDK's parser overlays a canonical event key (mirrored from the X-Webhook-Event header) so consumers can branch on a single field.

FieldTypeNotes
eventLiteral["payment.settled", "payment.failed"] (ReadOnly)Canonical event name, overlaid from the header.
typeLiteral["payment.settled", "payment.failed"] (ReadOnly)The event name as carried in the raw stored body.
challenge_idstrThe x402 payment challenge this payment settles or fails.
networkstrSettlement network, e.g. "base" or "base-sepolia".
amountstrToken base units (USDC has 6 decimals, so "10000" is 0.01 USDC).
assetstrThe checksummed token contract address.
payer_orgstr | NonePaying organization id, or None when not on-net.

Fields are marked ReadOnly (PEP 705) so that a subclass can narrow the Literal type without a type-checker error, a mutable TypedDict field is invariant and cannot be narrowed in a subclass otherwise.

PaymentSettledEvent#

A payment.settled webhook event. Subclasses PaymentEvent and narrows event and type to Literal["payment.settled"], so a type checker rejects treating it as a failed event once a guard has narrowed to it.

FieldTypeNotes
...all PaymentEvent fieldsevent / type narrowed to "payment.settled"
settle_txstrThe on-chain settlement transaction hash.

PaymentFailedEvent#

A payment.failed webhook event. Subclasses PaymentEvent and narrows event and type to Literal["payment.failed"].

FieldTypeNotes
...all PaymentEvent fieldsevent / type narrowed to "payment.failed"
failure_reasonstrHuman-readable reason the payment failed.

InteractionEvent#

An interaction.* webhook body, covering the interaction.x402.* and interaction.ack.* families. The stored payload is just {"interaction": {...}} with no event/type field; the SDK's parser overlays a canonical event key from the header.

FieldTypeNotes
eventstrCanonical event name, overlaid from the header (e.g. "interaction.x402.settled").
interactiondict[str, Any]The raw interaction payload.
idstrPresent on some interaction bodies.

InteractionX402Event is an alias for InteractionEvent, exported for callers who want a name that matches the interaction.x402.* subset specifically. There is no separate TypedDict shape for it; the alias is purely for readability at call sites.

Type guards#

Each guard is a TypeGuard predicate over object, so it narrows an already-parsed event value (for example, the return of handle_webhook_event) without requiring a prior isinstance check.

FunctionNarrows toTrue when
is_payment_event(event)PaymentEventevent["event"] is "payment.settled" or "payment.failed"
is_payment_settled_event(event)PaymentSettledEventevent["event"] == "payment.settled"
is_payment_failed_event(event)PaymentFailedEventevent["event"] == "payment.failed"
is_interaction_x402_event(event)InteractionEventevent["event"] starts with "interaction.x402."

All four accept a plain dict or any object exposing an .event attribute; internally they read via event.get("event") for dicts and getattr(event, "event", None) otherwise, returning None (and hence False) for anything that isn't a string.

from primitive import handle_webhook_event
from primitive.events import is_payment_settled_event, is_interaction_x402_event

event = handle_webhook_event(
    body=raw_body,
    headers=request.headers,
    secret=os.environ["PRIMITIVE_WEBHOOK_SECRET"],
)

if is_payment_settled_event(event):
    # event["amount"] is in token base units
    print(event["challenge_id"], event["amount"], event["settle_tx"])
elif is_interaction_x402_event(event):
    print(event["event"], event["interaction"])
Note

is_interaction_x402_event matches only the interaction.x402.* prefix. The interaction.ack.* events are also InteractionEvent-shaped on the wire, but there is no dedicated guard for them in this module; branch on event["event"] directly if you need to distinguish them.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer