Event catalog
The 20 event types Vipter sends by webhook, when each one fires and what comes in data.object, with a full example of an order, a subscription, a customer and an abandoned checkout.
Every event arrives in the same envelope. The type is in type and in the Vipter-Event-Type header, and the object is in data.object. Each type belongs to a family, and the family defines the shape of the object:
| Family | Types | data.object.object |
|---|---|---|
| Orders | order.* (5) | "order" |
| Subscriptions | subscription.* (11) | "subscription" |
| Customers | customer.* (2) | "customer" |
| Abandoned checkout | checkout.* (2) | "checkout_abandonment" |
Rules that apply to every family:
- Every field listed in the tables is always present in the object. When there is no value, the field comes as
null, never missing. The exception is the test event. - Money amounts come in cents, as an integer:
19700is R$ 197.00. The currency is in thecurrencyfield of the same object. - Dates inside
data.objectare ISO 8601 strings. The envelope'screateduses another format: Unix seconds. - The IDs in the examples are fictitious. Treat every ID as an opaque string and don't depend on its prefix or length.
- An endpoint receives only the types ticked on it. With none ticked, it receives all of them, including types created in the future. Answer 2xx to ignore the types your system doesn't use.
Orders
| Event | When it is sent |
|---|---|
order.paid | An order's payment was approved: a checkout purchase (an approved card or a paid PIX, Brazil's instant payment), a subscription renewal, a one-click upsell, a saved-card sale made from the dashboard, an extra charge on a subscription, or an order marked as paid by the team. |
order.failed | An order's charge was declined. It goes out once per order: further declines on the same order don't generate another event. |
order.refunded | The order was fully refunded, from the dashboard, by the provider, or marked as refunded by the team. |
order.partially_refunded | Part of the order was refunded. It can arrive more than once for the same order, one for each partial refund. |
order.charged_back | The buyer disputed the purchase with their card's bank (chargeback). |
Details the code guarantees:
- Renewals arrive as
order.paidwithrecurrence: "subsequent"andsubscription_idfilled in, in addition to the subscription'ssubscription.renewed. - Manual status. When someone on the team marks the order as paid or refunded, the event goes out with
status_source: "manual". After that, changes the provider reports about that order don't generate events. See manual status. - The object's status is the current one. An order that Vipter only learns about later, through the periodic sync, can generate
order.paidalready with anotherstatus, such asrefunded. Readstatusinstead of inferring it from the event type. - The order doesn't include UTMs, campaign source or seller. That data stays in the dashboard.
The order object
| Field | Type | Content |
|---|---|---|
object | string | Always "order". |
id | string | Order ID. |
customer_id | string or null | Customer ID. |
customer_email | string or null | Buyer's e-mail. |
subscription_id | string or null | Subscription that generated the order, on renewals and extra charges. |
status | string | pending, pre_authorized, authorized, failed, canceled, refund_pending, partially_refunded, refunded or charged_back. See order statuses. |
total_amount | integer | Total charged, in cents. |
currency | string | ISO 4217 code, such as BRL. |
refunded_amount | integer or null | Total already refunded, in cents. |
order_type | string or null | checkout, renewal, api, trial_setup or card_setup. |
recurrence | string or null | none (one-time purchase), initial (first charge of a subscription), subsequent (renewal) or unscheduled. |
offer_id | string or null | Offer of the first item. |
payment_method | string or null | credit_card, debit_card, pix, boleto (Brazilian bank payment slip) or wallet. |
provider_slug | string or null | Provider that processed the payment, such as pagarme. |
paid_at | date or null | When the payment was approved. |
items | list | The order items. See below. |
external_order_id | string or null | External reference for the order, when there is one. |
status_source | string | manual when the team set the status in the dashboard, provider in all other cases. |
created_at, updated_at | date or null | Creation and last change of the order. |
downloads | list | Only on order.paid that is not a renewal. The buyer's download links. An empty list when the order has no product with files. |
Each entry in items has these fields. Treat any extra fields as optional:
| Field | Type | Content |
|---|---|---|
offer_id, offer_name | string or null | Offer sold. |
product_id, product_name | string or null | The offer's product. |
billing_cycle | string or null | The offer's cycle. none for a one-time sale. |
quantity | integer | Units. In a pack of 3, it is 3. |
unit_amount, total_amount | integer | Unit price and line total, in cents. |
currency | string | Currency of the line. |
installments | integer or null | Number of installments. |
role | string | When present: main for the main product, bump for an order bump. An item without role is the main product. |
pack_label | string | When present: the name of the pack sold. |
bump_id | string | When present: the order bump that generated the line. |
Each entry in downloads has product_id, product_name, expires_at (date or null) and files, a list of { "name", "url" }. The links open on the store's checkout domain and stop working after a refund or chargeback. See digital delivery.
Example: order.paid
{
"id": "evt_3f9a1c7e5b2d4f6a8c0e1b3d",
"object": "event",
"type": "order.paid",
"created": 1790604191,
"livemode": true,
"api_version": "2026-09-01",
"data": {
"object": {
"object": "order",
"id": "ord_5c1e8a2b9d4f4e7a8b3c6d1e2f7a9b0c",
"customer_id": "cus_8b2d4f6a1c3e5a7b9d0f2e4c",
"customer_email": "ana.souza@example.com",
"subscription_id": null,
"status": "authorized",
"total_amount": 19700,
"currency": "BRL",
"refunded_amount": null,
"order_type": "checkout",
"recurrence": "none",
"offer_id": "ofr_a3454b7c008d455fbc5c3fc436c1879d",
"payment_method": "credit_card",
"provider_slug": "pagarme",
"paid_at": "2026-09-28T14:03:09.000Z",
"items": [
{
"offer_id": "ofr_a3454b7c008d455fbc5c3fc436c1879d",
"offer_name": "Acesso vitalício",
"product_id": "prd_5df6381e9a0b4c2d8e7f6a5b4c3d2e1f",
"product_name": "Curso de Fotografia",
"billing_cycle": "none",
"quantity": 1,
"unit_amount": 19700,
"total_amount": 19700,
"currency": "BRL",
"installments": 1
}
],
"external_order_id": null,
"status_source": "provider",
"created_at": "2026-09-28T14:02:51.000Z",
"updated_at": "2026-09-28T14:03:09.000Z",
"downloads": [
{
"product_id": "prd_5df6381e9a0b4c2d8e7f6a5b4c3d2e1f",
"product_name": "Curso de Fotografia",
"expires_at": null,
"files": [
{
"name": "Apostila.pdf",
"url": "https://pay.vipter.com/d/EXEMPLO-TOKEN/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a"
}
]
}
]
}
}
}Subscriptions
| Event | When it is sent |
|---|---|
subscription.created | A subscription was created, usually by the purchase of a recurring offer at checkout. |
subscription.renewed | The subscription was renewed. The renewal charge arrives separately, as order.paid. |
subscription.dunning | The renewal charge failed and the subscription went into dunning (status: "dunning"). |
subscription.reactivated | The subscription became active again: it left dunning, or it was reactivated by the team or by the subscriber. |
subscription.upgraded | The subscriber changed plans, to an amount greater than or equal to the previous one. |
subscription.downgraded | The subscriber changed plans, to an amount lower than the previous one. |
subscription.payment_method_changed | The card used for the subscription's charges was changed. |
subscription.paused | The subscription was paused. |
subscription.resumed | The paused subscription became active again. |
subscription.cancelled | The subscription was cancelled. |
subscription.expired | The subscription ended (status: "expired"). |
Details the code guarantees:
- Changes made by the team in the dashboard (cancel, pause, resume, reactivate, change plan) and by the subscriber in the customer portal (cancel, reactivate, change plan) generate the event right away. The provider's notice about the same change can generate another event of the same type, with another
id. See idempotency. - Scheduling the cancellation for the end of the period doesn't generate an event when it is scheduled. The object gets
cancel_at_period_end: true, which shows up in the subscription's next event. - On plan changes from the dashboard and the customer portal, Vipter compares
current_amountbefore and after: greater or equal becomessubscription.upgraded, lower becomessubscription.downgraded.
The subscription object
| Field | Type | Content |
|---|---|---|
object | string | Always "subscription". |
id | string | Subscription ID. |
customer_id, customer_email, customer_name | string or null | The subscriber. |
status | string | trialing, active, dunning, paused, cancelled or expired. |
current_offer_id, offer_name | string or null | Current plan. It changes on a plan change. |
product_id, product_name | string or null | The plan's product. |
billing_cycle | string or null | daily, biweekly, monthly, quarterly, half_yearly, yearly or custom. |
currency | string or null | Currency of the charges. |
current_amount | integer or null | Amount of each charge, in cents. |
current_period_start, current_period_end | date or null | Current paid period. |
next_billing_at | date or null | Next charge. |
trial_start, trial_end | date or null | Free trial period, when there was one. |
cycles_completed | integer or null | Cycles already charged. |
cycle_limit | integer or null | Maximum number of cycles, or null if there is no limit. |
cancel_at_period_end | boolean or null | true when the cancellation is scheduled for the end of the period. |
cancelled_at | date or null | When the subscription was cancelled. |
cancellation_reason | string or null | Reason given at cancellation. |
payment_instrument_id | string or null | ID of the saved card used for the charges. |
created_at, updated_at | date or null | Creation and last change of the subscription. |
Example: subscription.renewed
{
"id": "evt_7d0b2e4f6a8c1e3b5d7f9a0c",
"object": "event",
"type": "subscription.renewed",
"created": 1790611502,
"livemode": true,
"api_version": "2026-09-01",
"data": {
"object": {
"object": "subscription",
"id": "sub_23e6db9f0a1b4c5d8e7f6a5b4c3d2e1f",
"customer_id": "cus_8b2d4f6a1c3e5a7b9d0f2e4c",
"customer_email": "ana.souza@example.com",
"customer_name": "Ana Souza",
"status": "active",
"current_offer_id": "ofr_0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f",
"offer_name": "Plano mensal",
"product_id": "prd_1a2b3c4d5e6f4a7b8c9d0e1f2a3b4c5d",
"product_name": "Clube de Receitas",
"billing_cycle": "monthly",
"currency": "BRL",
"current_amount": 4990,
"current_period_start": "2026-09-28T16:05:00.000Z",
"current_period_end": "2026-10-28T16:05:00.000Z",
"next_billing_at": "2026-10-28T16:05:00.000Z",
"trial_start": null,
"trial_end": null,
"cycles_completed": 4,
"cycle_limit": null,
"cancel_at_period_end": false,
"cancelled_at": null,
"cancellation_reason": null,
"payment_instrument_id": "pi_4e6a8c0b2d4f6a8c0e2b4d6f",
"created_at": "2026-05-28T16:05:00.000Z",
"updated_at": "2026-09-28T16:05:02.000Z"
}
}
}Customers
| Event | When it is sent |
|---|---|
customer.created | Vipter registered a new customer: added by the team in the dashboard, or seen for the first time in a subscription or in the periodic sync with the provider. |
customer.updated | Someone on the team edited the customer in the dashboard. Each time the form is saved, an event goes out. |
Don't rely on customer.created to learn about every new buyer: it doesn't go out on every purchase path. To react to a purchase, use order.paid, which includes customer_id and customer_email. Changes the buyer makes in the customer portal, such as name and phone, don't generate customer.updated.
The customer object
| Field | Type | Content |
|---|---|---|
object | string | Always "customer". |
id | string | Customer ID. |
email | string | E-mail. |
name | string or null | Name. |
phone | string or null | Phone, such as +5511987654321. |
document_type | string or null | cpf, cnpj, passport or tax_id. CPF and CNPJ are the Brazilian individual and company taxpayer IDs. The document number doesn't come in the event. |
metadata | object or null | Customer metadata. |
created_at, updated_at | date or null | Creation and last change. |
Example: customer.created
{
"id": "evt_1c3e5a7b9d0f2e4c6a8b0d2f",
"object": "event",
"type": "customer.created",
"created": 1790604190,
"livemode": true,
"api_version": "2026-09-01",
"data": {
"object": {
"object": "customer",
"id": "cus_8b2d4f6a1c3e5a7b9d0f2e4c",
"email": "ana.souza@example.com",
"name": "Ana Souza",
"phone": "+5511987654321",
"document_type": "cpf",
"metadata": null,
"created_at": "2026-09-28T14:02:50.000Z",
"updated_at": "2026-09-28T14:02:50.000Z"
}
}
}The test event
The Send test event button creates a customer.created with a reduced object. It has only these fields and "test": true:
{
"object": "customer",
"id": "cust_test",
"email": "test@example.com",
"name": "Test Customer",
"test": true,
"created_at": "2026-09-28T14:10:00.000Z"
}Discard events with data.object.test === true before storing anything. Each click creates a new event, with another id. See Retries, disabling and resending.
Abandoned checkout
| Event | When it is sent |
|---|---|
checkout.abandoned | The buyer filled in their e-mail at checkout, did not try to pay and went idle. The record is created after 15 minutes of inactivity, and the event goes out 60 minutes after the record, if they didn't buy in the meantime. The check runs every 10 minutes, so the actual time varies. It goes out once per record. |
checkout.recovered | An abandoned checkout whose checkout.abandoned had already gone out ended in a paid purchase by the same buyer for the same product, up to 7 days after the abandonment. |
A declined card and a PIX generated but not paid don't count as abandonment. Several visits by the same buyer to the same product become a single record, with session_count greater than 1. The full rules are in Abandoned checkout recovery.
The checkout_abandonment object
| Field | Type | Content |
|---|---|---|
object | string | Always "checkout_abandonment". |
id | string | ID of the abandonment record. It is the same in both events. |
customer | object | id (null if the buyer is not a customer yet), email, name, phone and country. |
offer_id, offer_name | string or null | Offer of the checkout. |
product_id, product_name | string or null | The offer's product. |
quantity | integer | Units: the pack size, or 1. |
pack_label | string or null | Name of the pack, when the link was for a pack. |
amount | integer or null | Amount of the product the buyer saw, with the pack price, before coupons and shipping. In cents. |
currency | string or null | Currency of the checkout. |
locale | string or null | Language the checkout was in, such as pt. |
utm | object or null | The utm_source, utm_medium, utm_campaign, utm_content, utm_term and utm_id parameters that came with the buyer. |
referrer | string or null | Page the buyer came from. |
checkout_session_id | string | Most recent checkout session. |
session_count | integer | How many visits were merged into this record. |
recovery_url | string | Link that reopens the checkout with the buyer's details filled in. See recovery link. |
first_seen_at | date | Start of the first visit. |
abandoned_at | date | When the abandonment was recorded. |
checkout.recovered has the same object plus four fields:
| Field | Type | Content |
|---|---|---|
resolution | string | Always "recovered". |
order_id | string | The paid order that closed the abandonment. |
resolved_at | date | When the abandonment was closed. |
recovered_by_link | boolean | true if the buyer opened the recovery_url before buying. |
Example: checkout.abandoned
{
"id": "evt_9e1a3c5e7b9d0f2a4c6e8b0d",
"object": "event",
"type": "checkout.abandoned",
"created": 1790609400,
"livemode": true,
"api_version": "2026-09-01",
"data": {
"object": {
"object": "checkout_abandonment",
"id": "6f1d2c3b-4a5e-4f6a-9b8c-7d6e5f4a3b2c",
"customer": {
"id": null,
"email": "bruno.lima@example.com",
"name": "Bruno Lima",
"phone": "+5521998765432",
"country": "BR"
},
"offer_id": "ofr_7b8c9d0e1f2a4b3c8d7e6f5a4b3c2d1e",
"offer_name": "Kit 3 unidades",
"product_id": "prd_9f8e7d6c5b4a4f3e8d2c1b0a9f8e7d6c",
"product_name": "Chá Detox",
"quantity": 3,
"pack_label": "Kit com 3",
"amount": 24900,
"currency": "BRL",
"locale": "pt",
"utm": {
"utm_source": "instagram",
"utm_medium": "stories",
"utm_campaign": "black-friday"
},
"referrer": "https://l.instagram.com/",
"checkout_session_id": "cs_2b4d6f8a0c2e4a6b8d0f2a4c",
"session_count": 2,
"recovery_url": "https://pay.vipter.com/kit-cha?pack=3&rec=EXEMPLO-TOKEN",
"first_seen_at": "2026-09-28T13:12:40.000Z",
"abandoned_at": "2026-09-28T13:40:05.000Z"
}
}
}What to do next
- Understand the envelope fields and how to handle repeats.
- Verify the signature before processing any event.
Developer overview
What you can integrate with Vipter today (signed webhooks, checkout URL parameters, the UTM script and this documentation in Markdown), what doesn't exist and where to start.
Envelope format
The request Vipter sends to your endpoint, field by field, with the headers, the response deadline, how to handle repeated events and what Vipter guarantees about ordering.