Skip to main content
PayNext emits a webhook event for each concrete lifecycle transition. Subscribe only to the events your application needs and route on the type field—no payload diffing required. The v2 taxonomy is granular: one event type means one thing happened. Each v2 event carries api_version: "2.0.0". New endpoints default to v2. See Versioning for how v1 and v2 run in parallel.
Not every payment emits every event. Sequences vary by flow and processor—auto-capture skips payment_v2.authorized, and some methods go straight to payment_v2.settling or payment_v2.settled.

Payment events

Payments move through the PENDING, INCOMPLETE, AUTHORIZED, SETTLING, SETTLED, FAILED, DECLINED, CANCELLED, and BLOCKED statuses. Each transition emits a dedicated event.
Payment and subscription events are the source of truth for fulfillment—not the SDK’s onCheckoutComplete callback, which runs in the browser and isn’t guaranteed to fire. Grant access and fulfill orders from payment_v2.settled (and subscription_v2.created for subscriptions), not from the client-side callback.
Key off payment_v2.settled for revenue and fulfillment. It fires once, on the first transition to SETTLED—later attribute changes arrive as payment_v2.updated and never re-trigger revenue.
Fraud-alert data (Visa RDR, Order Insight, Compelling Evidence) flows through payment_v2.updated on the embedded fraud_prevention block—there is no dedicated fraud event. Disputes have their own events; see Dispute events.

Refund events

A refund rides on the payment object, so refund events belong to the payment_v2 group and carry the payment with an embedded refund field. Correlate by payment_id. Refunds move through PENDING, SETTLING, SETTLED, DECLINED, and FAILED.
A payment can have more than one refund (partial refunds). Refund details are embedded on the payment object; correlate events by payment_id and the refund’s processor_transaction_id. A payment reaching REFUNDED status is surfaced through these payment_v2.refund.* events—there is no payment_v2.refunded payment event.

Refunds that fail after settling

Some processors can fail or cancel a refund after reporting it as successful, sometimes days later. When that happens, you receive payment_v2.refund.failed or payment_v2.refund.declined for a refund you already saw as SETTLED:
  • The refund’s status changes to FAILED or DECLINED, and the payment’s refunded_amount returns to 0. The money did not reach the customer.
  • refund.status_reason.decline_code carries the processor’s reason, for example expired_or_canceled_card.
  • The event refers to the same refund, with the same processor_transaction_id.
Treat SETTLED as final only if no failure event follows for that refund. See your processor’s page under Integrations for processor-specific behavior.

Subscription events

Subscription events map to lifecycle transitions. The status field (trial, active, past_due, scheduled_for_cancellation, cancelled) tells you the resulting state.
To distinguish state changes that share a status: trial → paid and reactivation emit subscription_v2.activated; a recurring renewal or past-due recovery emits subscription_v2.renewed; an un-cancel (scheduled → active) emits subscription_v2.updated.

Dunning and retries

When a renewal payment fails, the subscription enters past_due and PayNext retries automatically. Each retry emits one subscription_v2.past_due event carrying an incremented past_due.attempt_count, so you can tell attempts apart. If all retries fail, the subscription transitions to cancelled.

Pre-charge reminders

subscription_v2.trial_will_end and subscription_v2.renewal_upcoming (and their v1 aliases subscription.trial_will_end / subscription.renewal_upcoming) fire 24 hours before PayNext charges a subscription—the first at the end of a trial, the second before an ordinary renewal. Both carry the standard subscription object; read next_billing_date for the exact charge moment. PayNext sends the trigger; you own the customer-facing message (email or SMS). Use these events to meet the card networks’ pre-charge notification requirements for trials and recurring billing.
  • No reminder precedes a dunning retry. Only regular, first-attempt charges are announced; a failed renewal enters the past-due ladder silently (see Dunning and retries).
  • Treat a reminder as idempotent by next_billing_date. On v2 each cycle’s event is delivered once; on v1 a rare delivery retry may repeat it. The next_billing_date in the payload identifies the cycle.
  • If a subscription’s next charge date moves (a plan change, for example), the reminder follows it to the new date automatically.

Payment method events

A saved payment method keeps the same ID for its whole life, but its details change underneath that ID—a reissued card brings a new number and expiry, and a network token is refreshed, suspended, or removed by the card network. This event is the only signal that those details moved. It fires once per applied change—a lifecycle status change, a card reissue, refreshed card details, or a removed network token. Duplicate and out-of-order notifications from the card network don’t produce an event.
If you cache card details such as last4, the expiry, or the brand against a payment method ID, refresh your copy when this event arrives. The ID stays the same after a reissue, so nothing else tells you the card behind it changed.
A card whose status is expired can still be chargeable while its network token is active. Read the two together before prompting a customer for a new card—see Payment methods.

Dispute events

When a cardholder or their bank contests a payment, PayNext records a dispute and sends dispute events as it changes. Dispute events belong to the payment_v2 group and carry the full dispute object in data.object, like other events. They exist in v2 only.
Dispute events are being rolled out and may not be available yet. An endpoint that receives all event types gets them automatically; an endpoint subscribed to selected event types receives them only after you add the payment_v2.dispute.* types.
Each arrow is labeled with the event it sends, without the payment_v2.dispute. prefix. The diagram shows the common paths; the table below is the complete rule.
WON, LOST, ACCEPTED and PREVENTED are final, with exceptions that come from the processors:
  • A resolved PayPal dispute can reopen. PayPal keeps the same dispute when, for example, the customer files an external chargeback, changes the reason or appeals. You receive reopened (plus evidence_submitted or under_review when it reopens into that status), and resolved_at is cleared.
  • A resolved PayPal dispute can move to a different final status, for example from WON to LOST. You receive the event for the new status, and resolved_at keeps its original value.
  • A Stripe dispute in LOST can later become WON (a late win). You receive won, and resolved_at keeps its original value.
  • An Unlimit retrieval request that closed as PREVENTED becomes LOST when a chargeback follows it. You receive lost, and resolved_at keeps its original value.
A final PayPal dispute can also change stage, reason, disputed_at, respond_by or details without changing its status. You receive updated. A final dispute on any processor can also gain new details keys, or values for keys that were null, while nothing else changes—for example when is_visa_rdr is added to a resolved Stripe dispute—and you receive updated for that too. This can arrive long after the dispute was resolved. Braintree and Unlimit disputes don’t reopen. A Braintree escalation arrives as a new dispute, and so does a chargeback that follows an Unlimit retrieval request.
No event fires when an update leaves status, stage, reason, disputed_at, respond_by and details unchanged—for example, a repeated delivery of an update PayNext already applied. event.id is stable across delivery retries. When the same event type fires again for a dispute—a second reopen, for example—it carries a new event.id.
Dispute events aren’t guaranteed to arrive in order. Reconcile on data.object.status and data.object.updated_at rather than on arrival order, and ignore an event whose updated_at is older than the state you already stored. updated_at has one-second precision, so two updates in the same second can share a value—when they do, fetch the dispute with GET /disputes/{id} for its current state.

Legacy (v1) events

The v1 event types are deprecated. They keep working for existing endpoints, but new integrations should use v2. See Versioning.
v1 uses coarse events: payment.updated and subscription.updated fire on every state change, so consumers must diff the payload to learn what happened. Map them to v2 as follows. ¹ v2 deliberately corrects v1 here: the initial activation of a no-trial subscription (previous status none) emits subscription.updated under v1 but subscription_v2.created under v2. During the overlap the same transition goes out under both names—this is intended, not a duplicate.

Classifying events for analytics

Some distinctions aren’t signalled as separate event types. Derive them from the payload: See the payment object, subscription object, and dispute object for full payload schemas.