Vatly
PHP Fluent

Event flow

Choose the event that matches your task.

Choose the event that matches your task. To listen for a paid order, implement your event dispatcher and register it in Wiring. This small adapter forwards typed events to an application callback:

use Vatly\Fluent\Contracts\EventDispatcherInterface;

final class BillingEvents implements EventDispatcherInterface
{
    public function __construct(private Closure $listener) {}

    public function dispatch(object $event): void
    {
        ($this->listener)($event);
    }
}

In production, let that callback write outbox jobs on the same transaction as your repositories. It receives the typed event after the built-in reactions have run. Follow Webhooks for verification, deduplication, and the transaction boundary.

Events that save records

EventBuilt-in persistence
order.paidFind by Vatly ID; store a new order or update the existing one. New store data includes lines.
order.payment_failedStore or update the order with the payload's status, usually pending during recovery. It does not invent a failed order status.
order.canceledUpdate an existing order's status and cancellation reason; skip if missing.
subscription.startedStore a new subscription or update an existing one.
subscription.updatedUpdate an existing subscription's plan, name, and quantity for an immediate change.
subscription.billing_updatedUpdate an existing subscription's mandate summary.
subscription.resumedClear the end date on an existing subscription.
subscription.canceled_immediately, subscription.canceled_with_grace_periodUpdate the existing subscription's confirmed end date and reason.
subscription.canceled_for_nonpaymentEnd the existing subscription after recovery is exhausted, with reason payment_failure.
subscription.cancellation_grace_period_completedStamp the actual end date on an existing subscription.
refund.completed, refund.failed, refund.canceledStore or update only when a refund repository is wired.
order.chargeback_received, order.chargeback_reversedStore or update only when a chargeback repository is wired. The order stays paid.

Order and subscription creation resolve hostCustomerId through your binding repository. It can be null. The store DTOs carry testmode; persist it. Order, refund, and chargeback store DTOs also carry totals, currency, and tax details.

All entity stores can return null for an unroutable record. Successful new paid-order and subscription stores dispatch OrderWasCreatedFromWebhook and SubscriptionWasCreatedFromWebhook. Updates do not dispatch those local-created events. This distinction is not a concurrency lock: enforce unique IDs and transactions in storage.

Partial updates use null for no change. Persist cancellation reasons if your host needs them. Subscription resume uses clearEndsAt: true; honor it in your writer.

Events your app handles

EventUseful for
subscription.update_scheduledShowing a pending next-cycle change from the typed scheduledUpdate. No current local plan change.
checkout.paidCheckout analytics or handoff. Use order.paid for full order persistence.
checkout.failed, checkout.canceled, checkout.expiredRetry and abandoned-cart flows.
webhook.setupEndpoint setup verification; acknowledge with 2xx.
Product update, approval, rejection, archive, and unarchive eventsCatalog notifications; no built-in storage.
Unknown event namesUnsupportedWebhookReceived, so you can observe new events without inventing a handler.

Refund and chargeback events still dispatch when their optional repositories are absent. Checkout events do not create local orders or bindings on their own. Anonymous checkout events may have no customer ID yet.

Money and snapshots

Typed event classes are owned by vatly-api-php in Vatly\API\Webhooks\Events. Money-bearing events use Money, not integer amounts. For paid orders and refunds, read currency from total->currency; use total->toCents() for local integer cents. Chargeback amounts are nullable at the event contract level and retain a separate currency field.

Fluent builds events from the signed resource snapshot without a follow-up GET. Its reactions flatten money to integer cents in persistence DTOs. They do not save subscription prices or build entitlement policy for you. Reversal totals are read live through the order handle, not synthesized into order statuses.

See Host tables for routing initial payments and renewals, and the API reference for complete payload fields.

Copyright © 2026