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 carriesevent - a
payment.*body carries the name intype - an
interaction.*body is just{"interaction": {...}}with noevent/typefield 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,
)
| Group | Values |
|---|---|
EMAIL_EVENT_TYPES | email.received, email.bounced, email.tls_report, email.dmarc_report, email.dmarc_failure |
PAYMENT_EVENT_TYPES | payment.settled, payment.failed |
INTERACTION_EVENT_TYPES | interaction.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."
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.
| Field | Type | Notes |
|---|---|---|
event | Literal["payment.settled", "payment.failed"] (ReadOnly) | Canonical event name, overlaid from the header. |
type | Literal["payment.settled", "payment.failed"] (ReadOnly) | The event name as carried in the raw stored body. |
challenge_id | str | The x402 payment challenge this payment settles or fails. |
network | str | Settlement network, e.g. "base" or "base-sepolia". |
amount | str | Token base units (USDC has 6 decimals, so "10000" is 0.01 USDC). |
asset | str | The checksummed token contract address. |
payer_org | str | None | Paying 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.
| Field | Type | Notes |
|---|---|---|
...all PaymentEvent fields | event / type narrowed to "payment.settled" | |
settle_tx | str | The on-chain settlement transaction hash. |
PaymentFailedEvent#
A payment.failed webhook event. Subclasses PaymentEvent and narrows event and type to Literal["payment.failed"].
| Field | Type | Notes |
|---|---|---|
...all PaymentEvent fields | event / type narrowed to "payment.failed" | |
failure_reason | str | Human-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.
| Field | Type | Notes |
|---|---|---|
event | str | Canonical event name, overlaid from the header (e.g. "interaction.x402.settled"). |
interaction | dict[str, Any] | The raw interaction payload. |
id | str | Present 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.
| Function | Narrows to | True when |
|---|---|---|
is_payment_event(event) | PaymentEvent | event["event"] is "payment.settled" or "payment.failed" |
is_payment_settled_event(event) | PaymentSettledEvent | event["event"] == "payment.settled" |
is_payment_failed_event(event) | PaymentFailedEvent | event["event"] == "payment.failed" |
is_interaction_x402_event(event) | InteractionEvent | event["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"])
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#
Parse and dispatch every webhook event family with handle_webhook_event.
Webhook Events OverviewThe shared signature-verification and forward-compatibility contract across all SDKs.
Creating and Paying ChallengesCreate a payment challenge as a payee and settle it as a payer.
Python SDK Type ReferenceBrowse the generated dataclasses and enums used elsewhere in the SDK.
Was this page helpful?