VipterHelp Center

API reference (v1)

Every endpoint of the Vipter API with its parameters, an example call and response, and the field table of each object: account, customer, subscription and its actions, subscription charge, metered usage (meters, usage events, items and periods), order, offer, product, checkout session, customer portal session, events and webhook endpoints.

Admin or OwnerAll plans

All endpoints live under https://api.vipter.com/v1, require an API key and follow the conventions: money as integers in the minor unit, dates in Unix seconds, lowercase currency, cursor-paginated lists. GET endpoints require the read scope; POST and DELETE endpoints require the write scope and accept an Idempotency-Key, which the subscription charge requires.

The machine-readable description, in OpenAPI 3.1, is at GET https://api.vipter.com/v1/openapi.json. It is meant for generating clients. A Postman collection generated from it, with one folder per resource and example bodies, is at GET https://api.vipter.com/v1/postman.json; it imports into Postman, Bruno and Insomnia. For AI agents, see Integrating with AI agents.

The examples use fictitious IDs and amounts in Brazilian reais. Authorization is shortened to vk_live_….

Account

Retrieve the account

GET /v1/account

Returns the store and the key that made the call. It is the first call of a new integration: if it answers 200, the key is right and the API is enabled.

curl https://api.vipter.com/v1/account \
  -H "Authorization: Bearer vk_live_…"
{
  "id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "object": "account",
  "name": "Loja Demo",
  "slug": "loja-demo",
  "country": "BR",
  "currency": "brl",
  "timezone": "America/Sao_Paulo",
  "api_key": {
    "id": "ak_4f8e2c1a9b7d6e5f3a2b1c0d",
    "name": "ERP",
    "scopes": ["read"],
    "default_version": "2026-11-01"
  },
  "api_version": "2026-11-01",
  "livemode": true
}
FieldTypeContent
idtextThe store's ID in Vipter.
name, slugtextThe store's name and slug. slug is null if the store has none.
countrytextThe store's country, ISO 3166-1 alpha-2.
currencytextThe store's default currency.
timezonetextThe store's time zone, in IANA format.
api_keyobjectThe key used: id, name, scopes (read, write) and default_version, the API version the key uses when the call sends no Vipter-Version.
api_versiontextThe version used on this call.
livemodebooleantrue in production.

Customers

A customer is someone who bought or subscribed in the store. Customers are created by the checkout, by the team in the dashboard or through POST /v1/customers.

List customers

GET /v1/customers
ParameterTypeContent
emailtextOnly customers with this email, case-insensitive.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/customers?email=ana@example.com" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/customers",
  "has_more": false,
  "data": [
    {
      "id": "cust_9d2e4f6a8b1c3d5e",
      "object": "customer",
      "email": "ana@example.com",
      "name": "Ana Souza",
      "phone": "+5511999990000",
      "document": { "type": "cpf", "number_masked": "*******1234" },
      "address": {
        "line1": "Rua das Flores, 100",
        "line2": "Apto 42",
        "city": "São Paulo",
        "state": "SP",
        "postal_code": "01310-100",
        "country": "BR"
      },
      "country": "BR",
      "locale": "pt-BR",
      "metadata": {},
      "livemode": true,
      "created": 1790186400
    }
  ]
}

Retrieve a customer

GET /v1/customers/{id}
curl https://api.vipter.com/v1/customers/cust_9d2e4f6a8b1c3d5e \
  -H "Authorization: Bearer vk_live_…"

Returns the customer object. An ID that does not exist in the store gets 404 resource_missing.

Create a customer

POST /v1/customers

Creates the customer in the store before the first purchase, to open a checkout session with customer or to store metadata. Requires the write scope. The payment provider requires a full name, a phone and a document, so they are mandatory here.

ParameterTypeContent
emailtextRequired. Stored in lowercase.
nametextRequired. Full name, 3 to 120 characters.
phonetextRequired. In international format (+5511999990000), or a national number of the store's country. A number that is not valid for the country gets 400 parameter_invalid with param phone.
document[type], document[number]textRequired. type is cpf, cnpj, passport or tax_id; number may come with dots and dashes, which are removed.
addressobjectBilling address: line1, city, state, postal_code and country (ISO 3166-1 alpha-2) are required inside the object; line2, number and district are optional.
metadataobjectFree data, with the same limits as the metadata of checkout sessions.
curl -X POST https://api.vipter.com/v1/customers \
  -H "Authorization: Bearer vk_live_…" \
  -H "Idempotency-Key: 2a7c0e4b-9d1f-4b3a-8e6c-5f2d7a1b0c9e" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ana@example.com",
    "name": "Ana Souza",
    "phone": "+5511999990000",
    "document": { "type": "cpf", "number": "123.456.789-09" },
    "metadata": { "user_id": "user_8213" }
  }'

The response is the customer object, with 201 when the customer was created. If a customer with that email already exists in the store, the response is 200 with the existing customer, without changing any field: to change the data, use Update a customer. A new customer generates the customer.created event.

codeHTTPMeaning
parameter_missing, parameter_invalid400A required field is missing or has the wrong format. param says which.
customer_rejected400The payment provider refused the registration. message carries the reason it gave, such as an invalid document.

Update a customer

POST /v1/customers/{id}

Accepts the same fields as Create a customer, all optional. Only what is sent is changed, with one exception: metadata replaces the whole map, as in Stripe. To delete a key, send the map without it. An ID that does not exist gets 404 resource_missing before any change.

curl -X POST https://api.vipter.com/v1/customers/cust_9d2e4f6a8b1c3d5e \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "metadata": { "user_id": "user_8213", "plan": "pro" } }'

Returns the updated customer object and generates the customer.updated event. The errors are the same as on creation.

The customer object

FieldTypeContent
idtextcust_…
objecttext"customer"
emailtextThe customer's email. It is what identifies the person in the checkout and in the customer portal.
nametext or nullName given at checkout.
phonetext or nullPhone in international format, with + and the country code.
documentobject or nulltype (such as cpf or cnpj) and number_masked, with only the last four digits. The API never returns the full document.
addressobject or nullBilling address: line1, line2, city, state, postal_code, country. Each field may be null.
countrytext or nullThe customer's country, ISO 3166-1 alpha-2.
localetext or nullThe customer's language, such as pt-BR, en or es.
metadataobjectThe data stored by POST /v1/customers or POST /v1/customers/{id}. {} on customers created by the checkout or the dashboard. A checkout session's metadata goes to the order and the subscription, not to the customer.
livemodebooleantrue in production.
createdintegerWhen the customer was created.

Subscriptions

List subscriptions

GET /v1/subscriptions
ParameterTypeContent
customertextOnly this customer's subscriptions (cust_…).
statustextOne of trialing, active, past_due, paused, canceled, expired. Another value gets 400 parameter_invalid.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/subscriptions?status=active&limit=1" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/subscriptions",
  "has_more": true,
  "data": [
    {
      "id": "sub_3c7a9e1f5b2d8c4e",
      "object": "subscription",
      "status": "active",
      "customer": "cust_9d2e4f6a8b1c3d5e",
      "customer_email": "ana@example.com",
      "offer": { "id": "ofr_6e2b8d4f1a9c3e7b", "name": "Plano Pro mensal" },
      "product": { "id": "prd_1a5c9e3b7d2f6a8c", "name": "Plano Pro" },
      "billing_cycle": "monthly",
      "custom_billing_days": null,
      "currency": "brl",
      "amount": 9900,
      "current_period_start": 1790186400,
      "current_period_end": 1792778400,
      "next_billing_at": 1792778400,
      "trial_start": null,
      "trial_end": null,
      "cycles_completed": 1,
      "cycle_limit": null,
      "cancel_at_period_end": false,
      "canceled_at": null,
      "ended_at": null,
      "cancellation_details": null,
      "default_payment_method": { "id": "pm_8f3d1c7e2a5b9d4f", "type": "card" },
      "installments": null,
      "past_due_details": null,
      "checkout_session": "cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
      "client_reference_id": "user_8213",
      "metadata": { "plan": "pro" },
      "livemode": true,
      "created": 1790186400
    }
  ]
}

Retrieve a subscription

GET /v1/subscriptions/{id}
curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e \
  -H "Authorization: Bearer vk_live_…"

Returns the subscription object.

The subscription object

FieldTypeContent
idtextsub_…
objecttext"subscription"
statustexttrialing (in a free trial), active, past_due (the last charge failed and Vipter is retrying), paused, canceled, expired (reached the cycle limit or the end without renewing).
customertext or nullThe subscriber's cust_….
customer_emailtext or nullThe subscriber's email, so you do not need another call.
offerobject or nullThe current offer: id (ofr_…) and name.
productobject or nullThe product: id (prd_…) and name.
billing_cycletext or nullThe billing interval: daily, biweekly, monthly, quarterly, half_yearly, yearly or custom.
custom_billing_daysinteger or nullWith billing_cycle custom, the interval in days.
currencytext or nullThe subscription's currency.
amountinteger or nullThe amount of each charge, in the minor unit.
current_period_start, current_period_endinteger or nullThe period already paid.
next_billing_atinteger or nullWhen the next charge is scheduled. null when there is no next one.
trial_start, trial_endinteger or nullThe free trial period, if there was one.
cycles_completedintegerHow many charges were already made.
cycle_limitinteger or nullHow many charges the subscription makes in total, on offers with a fixed number of cycles. null means no limit.
cancel_at_period_endbooleantrue when the cancellation was scheduled for the end of the paid period. status stays active until then.
canceled_atinteger or nullWhen the cancellation was requested.
ended_atinteger or nullWhen the subscription stopped being valid.
cancellation_detailsobject or nullreason (reason code), comment (free text) and source: who canceled, such as the dashboard, the customer portal or the provider.
default_payment_methodobject or nullThe saved card that pays the renewals: id and type (card). null when the subscription pays by PIX or another method without a saved card.
installmentsinteger or nullIn how many installments each charge is made, when the offer allows it.
past_due_detailsobject or nullOnly with status past_due: attempts (how many attempts failed so far), next_retry_at and since (when the first one failed).
checkout_sessiontext or nullcs_… of the checkout session that created the subscription. null on subscriptions that came from a regular link or from the dashboard.
client_reference_idtext or nullYour identifier, copied from the checkout session's client_reference_id or stored through POST /v1/subscriptions/{id}.
metadataobjectThe checkout session's subscription_data[metadata] or, when it was not sent, the session's metadata; or what you stored through POST /v1/subscriptions/{id}. {} on other subscriptions.
livemodebooleantrue in production.
createdintegerWhen the subscription started.

Subscription actions

The same actions the team performs on the subscription page in the dashboard, and the subscriber performs in the customer portal. All of them require the write scope, accept an Idempotency-Key and return the already updated subscription object: read status, cancel_at_period_end, offer and next_billing_at from the response instead of waiting for the webhook. The matching event goes out exactly as it does for an action in the dashboard; see the event catalog.

An ID that does not exist in the store gets 404 resource_missing. An action that does not fit the subscription's current state, such as pausing an already paused subscription or resuming one that is not paused, gets 400 provider_error, with the reason in message.

Update a subscription

POST /v1/subscriptions/{id}

Stores your reference and metadata on the subscription, or schedules the cancellation for the end of the paid period, like Stripe's cancel_at_period_end.

ParameterTypeContent
client_reference_idtext or nullYour identifier, up to 200 characters. null clears it.
metadataobjectReplaces the whole map, as in Stripe. To remove a key, send the map without it. Same limits as the metadata of checkout sessions.
cancel_at_period_endbooleantrue schedules the cancellation: status stays active until current_period_end, and the subscription does not renew. false is refused with 400 parameter_invalid: to undo a scheduled cancellation, call POST …/reactivate.
cancellation_details[reason]textWith cancel_at_period_end: true: the reason, one of too_expensive, not_using, missing_features, switching, temporary, other. Any other value is ignored.
cancellation_details[comment]textWith cancel_at_period_end: true: a free comment, up to 500 characters.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "cancel_at_period_end": true,
    "cancellation_details": { "reason": "switching", "comment": "Moving to the yearly plan in January." }
  }'

Returns the subscription object with cancel_at_period_end: true and cancellation_details carrying the reason and the comment. Changing client_reference_id or metadata emits no event, and neither does scheduling the cancellation: the object shows up with cancel_at_period_end: true in the subscription's next event and, when the period ends, customer.subscription.deleted goes out. When the call carries both the reference and the scheduling, the reference is stored first; if the scheduling is refused, it has already been saved.

Cancel now

DELETE /v1/subscriptions/{id}

Cancels right away, without waiting for the end of the paid period, like Stripe's DELETE. The reason goes in the query string, with the same values as in Update a subscription.

curl -X DELETE "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e?cancellation_details[reason]=not_using" \
  -H "Authorization: Bearer vk_live_…"

Returns the subscription object with status: "canceled" and emits customer.subscription.deleted (subscription.cancelled in the original catalog). In cancellation_details, the source of a cancellation through the API comes as dashboard, the same value as a cancellation by the team. The subscriber gets the same notices as for a cancellation in the dashboard.

Pause and resume

POST /v1/subscriptions/{id}/pause
POST /v1/subscriptions/{id}/resume

Pausing stops the renewals without canceling; resuming starts charging again. The body of pause accepts reason, free text up to 200 characters kept in the subscription's history; resume has no body.

curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/pause \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Customer asked for a two-month pause." }'

They return the subscription object with status: "paused" and, later, "active", and emit customer.subscription.paused and customer.subscription.resumed. A paused subscription does not accept one-off charges.

Reactivate

POST /v1/subscriptions/{id}/reactivate

Undoes a scheduled cancellation (cancel_at_period_end goes back to false and the subscription renews normally) or reactivates a canceled or expired subscription, when the subscription provider allows it; a reactivation it refuses gets 400 provider_error. No body.

curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/reactivate \
  -H "Authorization: Bearer vk_live_…"

Returns the subscription object and emits customer.subscription.updated (subscription.reactivated in the original catalog).

Change the offer

POST /v1/subscriptions/{id}/change_offer

Moves the subscription to another offer, such as an upgrade from the monthly plan to the yearly one.

ParameterTypeContent
offertextRequired. The ofr_… of the new offer. Only the ID; the slug is not accepted. An offer that does not exist in the store gets 404 resource_missing with param offer.

The new offer must belong to the same product, or the same product family, as the current one; otherwise the change is refused with 400 provider_error.

curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/change_offer \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "offer": "ofr_9a4c2e8b6d1f3a7c" }'

Returns the subscription object with offer, amount, billing_cycle and next_billing_at already from the new offer, and emits customer.subscription.updated (subscription.upgraded or subscription.downgraded in the original catalog, depending on whether the new amount is greater than or equal to, or less than, the previous one).

Subscription charges

A subscription charge is a one-off amount charged now on a subscription's saved card, outside the cycle: a usage overage, an add-on, an extra service. The next renewal date and the recurring amount do not change. It is the same charge the team makes in the dashboard through the Charge extra amount… button of the subscription (One-off charge); both show up in the same list, and the source field says where each one came from (API or Dashboard on the subscription page).

The charge asks nothing of the subscriber: Vipter charges the saved card without the customer present, through the same provider that took the subscription's first charge. Every approved charge becomes a paid order and counts as one order in the store's Vipter plan quota. The guide for billing usage in a SaaS is in Additional usage charges.

Charge the saved card

POST /v1/subscriptions/{id}/charges

Requires the write scope and requires an Idempotency-Key: without it, the call gets 400 idempotency_key_required before anything else. Use a value that identifies the charge you intend to make, such as the usage period's ID in your system. The same key never charges twice: it returns the same charge, with 200 and the Idempotent-Replayed: true header. Unlike the other calls, a charge's key does not expire after 24 hours: it stays tied to the charge forever, and repeating it months later still returns the same charge.

ParameterTypeContent
amountintegerRequired. The total to charge, in the currency's minor unit (4990 is R$ 49.90). The minimum is one unit of the currency (100 in brl or usd), otherwise 400 amount_too_small.
currencytextOptional. Must be the subscription's currency, otherwise 400 currency_mismatch. Without it, the subscription's currency is used.
descriptiontextUp to 140 characters. It is the charge description sent to the provider and, without lines, the name of the order's single item. Without description, the first line's description applies.
lines[]list1 to 50 items explaining the amount; they become the order's items. Each item: description (required, up to 140 characters), quantity (integer, default 1) and unit_amount (integer, in the minor unit). The sum of quantity × unit_amount must equal amount, otherwise 400 lines_total_mismatch. Without lines, the order has a single item, with description and amount.
metadataobjectFree data, with the same limits as the metadata of checkout sessions. It stays on the charge and comes back in the subscription_charge.* events.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/charges \
  -H "Authorization: Bearer vk_live_…" \
  -H "Idempotency-Key: usage:user_8213:2026-09" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5750,
    "currency": "brl",
    "description": "Additional usage for September",
    "lines": [
      { "description": "Calls beyond the allowance", "quantity": 2300, "unit_amount": 2 },
      { "description": "Additional storage (GB)", "quantity": 23, "unit_amount": 50 }
    ],
    "metadata": { "user_id": "user_8213", "period": "2026-09" }
  }'

The response is 201 with the subscription_charge object:

{
  "id": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
  "object": "subscription_charge",
  "status": "succeeded",
  "subscription": "sub_3c7a9e1f5b2d8c4e",
  "customer": "cust_9d2e4f6a8b1c3d5e",
  "amount": 5750,
  "currency": "brl",
  "description": "Additional usage for September",
  "lines": [
    { "description": "Calls beyond the allowance", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
    { "description": "Additional storage (GB)", "quantity": 23, "unit_amount": 50, "amount": 1150 }
  ],
  "order": "ord_2f8c4e6a1b3d5f7e",
  "transaction": "tx_9b1d3f5a7c2e4a6c",
  "failure_code": null,
  "failure_message": null,
  "source": "api",
  "metadata": { "user_id": "user_8213", "period": "2026-09" },
  "livemode": true,
  "created": 1790790400,
  "settled_at": 1790790403
}

What happens with an approved charge:

  • It becomes an order with billing_reason: "manual", order_type: "api", recurrence: "unscheduled", subscription filled in and the lines as items (kind: "charge"). The charge's sch_… is in the order's external_order_id.
  • The events invoice.paid (with the order) and subscription_charge.succeeded (with the charge) go out in the 2026-11-01 catalog, and order.paid in the original one.
  • The subscriber receives the store's purchase confirmation e-mail, when it is active, and sees the order in the customer portal.

A charge the provider leaves under review comes back with 201 and status: "pending", without settled_at. It is settled later, when the provider notifies, and then becomes succeeded or failed and emits the events. Look it up with GET /v1/subscription_charges/{id} or wait for subscription_charge.succeeded / subscription_charge.failed.

Card declined. The response is 402 with type card_error, as in Stripe. The code is the decline code reported by the provider, or declined when it reports none; codes vary by provider, so do not depend on a fixed list. The declined charge comes whole in error.subscription_charge, with status: "failed", failure_code and failure_message:

{
  "error": {
    "type": "card_error",
    "code": "insufficient_funds",
    "message": "Insufficient funds.",
    "doc_url": "https://docs.vipter.com/pt-br/developers/api-conventions#erros",
    "subscription_charge": {
      "id": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
      "object": "subscription_charge",
      "status": "failed",
      "subscription": "sub_3c7a9e1f5b2d8c4e",
      "customer": "cust_9d2e4f6a8b1c3d5e",
      "amount": 5750,
      "currency": "brl",
      "description": "Additional usage for September",
      "lines": [
        { "description": "Calls beyond the allowance", "quantity": 2300, "unit_amount": 2, "amount": 4600 },
        { "description": "Additional storage (GB)", "quantity": 23, "unit_amount": 50, "amount": 1150 }
      ],
      "order": "ord_2f8c4e6a1b3d5f7e",
      "transaction": "tx_9b1d3f5a7c2e4a6c",
      "failure_code": "insufficient_funds",
      "failure_message": "Insufficient funds.",
      "source": "api",
      "metadata": { "user_id": "user_8213", "period": "2026-09" },
      "livemode": true,
      "created": 1790790400,
      "settled_at": 1790790402
    }
  }
}

A decline emits subscription_charge.failed and, when the provider recorded an order for the attempt, invoice.payment_failed with that order (status: "failed"). Vipter does not retry on its own: the charge stays failed, and repeating the same Idempotency-Key returns the same 402. To charge again, after the subscriber changes the card in the customer portal, for example, make a new call with another key.

Errors of this call, besides the general errors:

codeHTTPMeaning
idempotency_key_required400The call came without an Idempotency-Key. The type is idempotency_error.
resource_missing404The subscription does not exist in the store.
subscription_not_chargeable400The subscription is paused, canceled or expired. Only active, trialing and past_due accept a charge.
no_payment_method400The subscription has no saved card: it pays by PIX or another method without a card.
payment_method_not_chargeable400The card is stored at a provider that does not accept charges without the customer present. Today, Mercado Pago.
currency_mismatch400currency differs from the subscription's currency. message says which it is.
amount_too_small400amount is less than one unit of the currency.
lines_total_mismatch400The lines do not add up to amount. message carries both values.
provider_error400The provider refused the charge request before it reached the card. message carries the reason. The charge is recorded as failed under that key; use another one to try again.
project_inactive403The store cannot charge because its Vipter subscription is past due. The type is permission_error.

List a subscription's charges

GET /v1/subscriptions/{id}/charges
ParameterTypeContent
statustextpending, succeeded or failed.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/charges?status=succeeded" \
  -H "Authorization: Bearer vk_live_…"

Returns a list object of subscription_charge, newest first, with the charges made through the API and those made in the dashboard.

Retrieve a charge

GET /v1/subscription_charges/{id}
curl https://api.vipter.com/v1/subscription_charges/sch_7e2a9c4b1d3f5a6e8b0c2d4f \
  -H "Authorization: Bearer vk_live_…"

Returns the subscription_charge object. It is the call for following a charge that stayed pending.

The subscription charge object

FieldTypeContent
idtextsch_…
objecttext"subscription_charge"
statustextpending (under review at the provider), succeeded (paid) or failed (declined, or the charge request was refused).
subscriptiontextsub_… of the charged subscription.
customertext or nullcust_… of the subscriber.
amountintegerThe total charged, in the minor unit.
currencytextThe currency, lowercase: always the subscription's.
descriptiontext or nullThe description sent, or the first line's.
lineslistThe lines sent: description, quantity, unit_amount and amount (unit_amount × quantity). [] when the charge came without lines.
ordertext or nullord_… of the order the charge generated. null when the provider recorded no order.
transactiontext or nullThe transaction's ID at the payment provider.
failure_codetext or nullWith status failed: the provider's decline code, declined when it reported none, or the error code when the charge request was refused.
failure_messagetext or nullWith status failed: the reason, in the provider's words.
sourcetextWhere the charge came from: api (this API), dashboard (the dashboard button) or usage (metered usage billing, made by Vipter when a period closes).
metadataobjectWhat you sent. {} on charges made in the dashboard.
livemodebooleantrue in production.
createdintegerWhen the charge was requested.
settled_atinteger or nullWhen it became succeeded or failed. null while pending.

Metered usage

Consumption billing with the count on Vipter's side, in the shape of Stripe's Billing Meters: a meter per kind of usage, usage events per customer, a usage item that prices the meter on the subscription, and a period per subscription cycle, which closes and becomes a subscription charge with source: "usage". The guide, with pricing examples and what happens at the end of the cycle, is in Metered usage (Meters). GET calls need the read scope; POST and DELETE, the write scope.

Create a meter

POST /v1/billing/meters
ParameterTypeContent
display_namestringRequired. The meter's name, 1 to 250 characters. Shown on the buyer's order line.
event_namestringRequired. The name events use: a-z, 0-9, _, . and - only, up to 100 characters, unique in the store. It does not change afterwards.
default_aggregation[formula]stringsum (adds the values, default), count (counts the events, ignores the value) or last (keeps the period's latest value).
customer_mapping[event_payload_key]stringThe payload key that carries the customer's cust_…. Default customer_id. customer_mapping[type] accepts only by_id.
value_settings[event_payload_key]stringThe payload key that carries the value. Default value.
curl -X POST https://api.vipter.com/v1/billing/meters \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "display_name": "API calls", "event_name": "api_calls", "default_aggregation": { "formula": "sum" } }'

The response is 201 with the billing.meter object. An event_name that already exists in the store gets 400 event_name_taken.

List meters

GET /v1/billing/meters
ParameterTypeContent
statusstringactive or inactive.
limitintegerPage size.

Returns a list object of billing.meter, newest first.

Retrieve and update a meter

GET /v1/billing/meters/{id}
POST /v1/billing/meters/{id}

The POST accepts only display_name; the meter's other fields do not change. An ID that does not exist in the store gets 404 resource_missing.

Deactivate a meter

POST /v1/billing/meters/{id}/deactivate

No body. The meter becomes inactive with status_transitions.deactivated_at filled in, and new events with its event_name get 400 meter_inactive. Open periods keep showing the meter's quantity, priced at zero. There is no reactivation through the API.

The billing.meter object

{
  "id": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
  "object": "billing.meter",
  "display_name": "API calls",
  "event_name": "api_calls",
  "default_aggregation": { "formula": "sum" },
  "customer_mapping": { "type": "by_id", "event_payload_key": "customer_id" },
  "value_settings": { "event_payload_key": "value" },
  "status": "active",
  "status_transitions": { "deactivated_at": null },
  "livemode": true,
  "created": 1791100800,
  "updated": 1791100800
}
FieldTypeContent
idstringmtr_…
objectstring"billing.meter"
display_namestringThe meter's name.
event_namestringThe name events use.
default_aggregationobjectformula: sum, count or last.
customer_mappingobjecttype (by_id) and event_payload_key, the payload key with the customer.
value_settingsobjectevent_payload_key, the payload key with the value.
statusstringactive or inactive.
status_transitionsobjectdeactivated_at: when the meter was deactivated, or null.
livemodebooleantrue in production.
created, updatedintegerCreation and last change.

Report a usage event

POST /v1/billing/meter_events
ParameterTypeContent
event_namestringRequired. The meter's event_name.
payloadobjectRequired. Up to 20 keys with string, number or boolean values. It must carry the customer under the meter's key (customer_id by default; stripe_customer_id is accepted as an alias) and, except on count meters, the numeric value under the value key (value by default). subscription_id picks the subscription when the customer has more than one priced for the meter.
identifierstringUp to 100 characters. Idempotency per meter: the same identifier returns the event already stored, with 200. Without it, Vipter generates one.
timestampintegerWhen the usage happened, in Unix seconds: up to 35 days in the past and up to 5 minutes ahead. Default: now.
curl -X POST https://api.vipter.com/v1/billing/meter_events \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 } }'

The response is 201 with the billing.meter_event object, or 200 with the stored event when the identifier repeats. On arrival, the event is attached to the customer's active, trialing or past_due subscription that has a usage item for the meter (the most recent, if there is more than one). An unattached event is stored with subscription: null, is not charged, and triggers billing.meter.error_report_triggered, at most once per meter and per hour. See Unattached events.

codeHTTPMeaning
no_meter_found400No meter has that event_name.
meter_inactive400The meter was deactivated.
invalid_payload400The customer is missing, or the value is not a number. param names the key (payload.customer_id, payload.value).
timestamp_out_of_range400timestamp outside the window of 35 days back to 5 minutes ahead.
resource_missing404The payload's customer does not exist in the store. param is the customer key.

Report events in batch

POST /v1/billing/meter_events/batch
ParameterTypeContent
events[]listRequired. 1 to 100 events, each with the fields of Report a usage event.
curl -X POST https://api.vipter.com/v1/billing/meter_events/batch \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      { "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 } },
      { "event_name": "api_calls", "identifier": "req_01J9X3K7M3", "payload": { "customer_id": "cust_0000000000000000", "value": 1 } }
    ]
  }'
{
  "object": "billing.meter_event_batch",
  "accepted": 1,
  "duplicates": 0,
  "errors": 1,
  "results": [
    { "status": "accepted", "event": { "id": "mev_1a5c9e3b7d2f6a8c0e4b2d6f", "object": "billing.meter_event", "event_name": "api_calls", "identifier": "req_01J9X3K7M2", "payload": { "customer_id": "cust_9d2e4f6a8b1c3d5e", "value": 1 }, "customer": "cust_9d2e4f6a8b1c3d5e", "subscription": "sub_3c7a9e1f5b2d8c4e", "value": 1, "timestamp": 1791101000, "livemode": true, "created": 1791101000 } },
    { "status": "error", "error": { "code": "customer_not_found", "message": "No such customer: 'cust_0000000000000000'", "param": "payload.customer_id" } }
  ]
}

Each event is accepted or refused on its own, in the order sent: results[i] answers events[i] with status accepted (stored now), duplicate (the identifier already existed; event is the stored one) or error (error with code, message and param, the same codes as the single call, with customer_not_found in place of resource_missing). The response is 200 whenever at least one event was accepted or repeated, and 400 only when none got in. A batch with an invalid field in the body (such as an empty events) gets 400 parameter_invalid without storing anything.

The billing.meter_event object

FieldTypeContent
idstringmev_…
objectstring"billing.meter_event"
event_namestringThe meter.
identifierstringYour identifier, or the one Vipter generated.
payloadobjectThe payload sent.
customerstringThe customer's cust_….
subscriptionstring or nullThe subscription the event will be charged on. null when no active subscription of the customer prices the meter.
valuenumberThe event's value. 1 on count meters.
timestampintegerWhen the usage happened. Decides which period the event falls into.
livemodebooleantrue in production.
createdintegerWhen the event arrived.

Aggregate usage of a customer

GET /v1/billing/meters/{id}/event_summaries
ParameterTypeContent
customerstringRequired. The cust_….
start_time, end_timeintegerRequired. The window, in Unix seconds: start_time inclusive, end_time exclusive. end_time must be later, and the window may span up to one year, otherwise 400 parameter_invalid.
value_grouping_windowstringhour or day: one summary per hour or per day (in UTC), only for the windows that have events. Without it, a single summary.
curl "https://api.vipter.com/v1/billing/meters/mtr_4f8e2c1a9b7d6e5f3a2b1c0d/event_summaries?customer=cust_9d2e4f6a8b1c3d5e&start_time=1790186400&end_time=1792778400&value_grouping_window=day" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/billing/meters/mtr_4f8e2c1a9b7d6e5f3a2b1c0d/event_summaries",
  "has_more": false,
  "data": [
    { "object": "billing.meter_event_summary", "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "customer": "cust_9d2e4f6a8b1c3d5e", "start_time": 1790186400, "end_time": 1790208000, "aggregated_value": 412, "livemode": true },
    { "object": "billing.meter_event_summary", "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "customer": "cust_9d2e4f6a8b1c3d5e", "start_time": 1790208000, "end_time": 1790294400, "aggregated_value": 1180, "livemode": true }
  ]
}

aggregated_value applies the meter's formula (sum, count or last) to the customer's events in the window, attached to a subscription or not. The list is not paginated.

Usage items of a subscription

GET /v1/subscriptions/{id}/usage_items
POST /v1/subscriptions/{id}/usage_items
DELETE /v1/subscriptions/{id}/usage_items/{itemId}

A usage item prices a meter on a subscription: one item per meter. It comes from the offer (configured in the dashboard, on the offer's page) on the first metered-usage run after the subscription is created, within 10 minutes, with source: "offer", or is set here, with source: "api". An item set through the API is never overwritten by the offer's inheritance. A POST for the same meter replaces the item.

ParameterTypeContent
meterstringRequired. The mtr_…. A meter that does not exist gets 404 resource_missing.
currencystringOptional. Must be the subscription's currency, otherwise 400 currency_mismatch.
unit_amountnumberRequired. Price of one unit, in the currency's minor unit, fractions allowed: 0.4 is R$ 0.004. 0 or more. With tiers, it is not part of the calculation.
included_unitsnumberAllowance: units of the period that are not charged. Default 0.
tiers[]list1 to 20 graduated tiers over the units beyond the allowance, each with up_to (upper bound, inclusive; null on the last one), unit_amount (per unit in the tier) and flat_amount (optional, charged once when the tier is used). up_to must be ascending and the last one null, otherwise 400 parameter_invalid with param tiers.
roundingstringup (round up, default) or nearest (closest), applied to the line's total, in whole cents.
billing_thresholdintegerIn cents. The period closes and charges before the end of the cycle when the accumulated total reaches it.
labelstringUp to 120 characters, for your own records.
curl -X POST https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage_items \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "unit_amount": 0.4, "included_units": 10000, "billing_threshold": 20000 }'

The POST answers 201 with the usage_item object; the GET returns a list object of usage_item, without pagination; the DELETE returns { "id": "usi_…", "object": "usage_item", "deleted": true }, or 404 resource_missing if the item does not belong to that subscription.

The usage_item object

{
  "id": "usi_9d2e4f6a8b1c3d5e7f0a2b4c",
  "object": "usage_item",
  "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d",
  "subscription": "sub_3c7a9e1f5b2d8c4e",
  "offer": null,
  "currency": "brl",
  "unit_amount": 0.4,
  "included_units": 10000,
  "tiers": null,
  "rounding": "up",
  "billing_threshold": 20000,
  "label": null,
  "source": "api",
  "livemode": true,
  "created": 1791100900
}
FieldTypeContent
idstringusi_…
objectstring"usage_item"
meterstringThe meter's mtr_….
subscriptionstringThe subscription's sub_….
offernullReserved. On a subscription's items it is always null.
currencystringThe currency, lowercase: the subscription's.
unit_amount, included_units, tiers, rounding, billing_threshold, labelAs sent. tiers and billing_threshold come null when absent.
sourcestringoffer (inherited from the offer), api or dashboard.
livemodebooleantrue in production.
createdintegerWhen the item was created.

Retrieve the usage periods

GET /v1/subscriptions/{id}/usage
curl https://api.vipter.com/v1/subscriptions/sub_3c7a9e1f5b2d8c4e/usage \
  -H "Authorization: Bearer vk_live_…"

Returns a list object with the subscription's 12 most recent periods, newest first, without pagination. The open period is computed at call time, from the events; closed ones come as they were at the close. A subscription without usage items returns an empty list.

The usage_period object

{
  "id": "usp_7b3e9f1c2a8d4e6f0b2d4f6a",
  "object": "usage_period",
  "subscription": "sub_3c7a9e1f5b2d8c4e",
  "status": "closed",
  "close_reason": "period_end",
  "period_start": 1790186400,
  "period_end": 1792778400,
  "currency": "brl",
  "lines": [
    { "meter": "mtr_4f8e2c1a9b7d6e5f3a2b1c0d", "event_name": "api_calls", "quantity": 12300, "included": 10000, "billable": 2300, "unit_amount": 0.4, "amount": 920 }
  ],
  "amount_total": 920,
  "charge": "sch_7e2a9c4b1d3f5a6e8b0c2d4f",
  "computed_at": 1792778700,
  "closed_at": 1792778700,
  "livemode": true,
  "created": 1790187000
}
FieldTypeContent
idstringusp_…
objectstring"usage_period"
subscriptionstringThe subscription's sub_….
statusstringopen (accumulating), closing (being charged) or closed.
close_reasonstring or nullperiod_end (the cycle ended), threshold (the billing_threshold was reached) or subscription_ended (the subscription stopped being chargeable). null while open.
period_start, period_endintegerThe period's window: the subscription's cycle, or the rest of it after a threshold close.
currencystringThe currency, lowercase.
lineslistOne per meter with an item: meter, event_name, quantity (the aggregate), included (the allowance), billable (quantity minus included), unit_amount and amount (in cents).
amount_totalintegerThe sum of the lines, in cents.
chargestring or nullThe sch_… of the period's charge, approved or declined. null while open, when the total was zero, or when the charge was refused before reaching the card.
computed_atinteger or nullWhen the lines were computed. On the open period, the time of the call.
closed_atinteger or nullWhen the period closed.
livemodebooleantrue in production.
createdintegerWhen the period opened.

Metered usage errors

Besides the general errors:

codeHTTPWhereMeaning
event_name_taken400Create a meterA meter with that event_name already exists.
no_meter_found400EventsNo meter has that event_name.
meter_inactive400EventsThe meter was deactivated.
invalid_payload400EventsThe customer is missing from the payload, or the value is not a number.
timestamp_out_of_range400Eventstimestamp outside the window of 35 days back to 5 minutes ahead.
customer_not_foundBatchOnly inside results[]: the customer does not exist. On the single call it is 404 resource_missing.
currency_mismatch400Usage itemscurrency differs from the subscription's currency.
parameter_invalid400Usage items, summariestiers out of order or without the last null tier; end_time before start_time or a window longer than one year.
resource_missing404AllMeter, subscription, item or customer that does not exist in the store.

Orders

An order is a charge: a one-off purchase, the first charge of a subscription, a renewal, a one-off charge on a subscription made in the dashboard or through the API. It is what Stripe calls an invoice; the name follows what the store owner sees in the dashboard.

List orders

GET /v1/orders
ParameterTypeContent
customertextOnly this customer's orders (cust_…).
subscriptiontextOnly this subscription's orders (sub_…).
statustextOne of pending, pre_authorized, authorized, failed, canceled, refund_pending, partially_refunded, refunded, charged_back.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/orders?subscription=sub_3c7a9e1f5b2d8c4e&limit=1" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/orders",
  "has_more": true,
  "data": [
    {
      "id": "ord_7b3e9f1c2a8d4e6f",
      "object": "order",
      "status": "authorized",
      "paid": true,
      "status_source": "provider",
      "billing_reason": "subscription_cycle",
      "customer": "cust_9d2e4f6a8b1c3d5e",
      "customer_email": "ana@example.com",
      "subscription": "sub_3c7a9e1f5b2d8c4e",
      "offer": "ofr_6e2b8d4f1a9c3e7b",
      "order_type": "renewal",
      "recurrence": "subsequent",
      "currency": "brl",
      "amount_total": 9900,
      "amount_refunded": 0,
      "amount_discount": 0,
      "amount_shipping": 0,
      "amount_interest": 0,
      "installments": 1,
      "payment_method": "credit_card",
      "provider": "pagarme",
      "coupon_codes": [],
      "lines": [
        {
          "description": "Plano Pro mensal",
          "quantity": 1,
          "unit_amount": 9900,
          "amount": 9900,
          "offer": "ofr_6e2b8d4f1a9c3e7b",
          "product": "prd_1a5c9e3b7d2f6a8c",
          "kind": "main"
        }
      ],
      "shipping": null,
      "external_order_id": null,
      "checkout_session": null,
      "client_reference_id": null,
      "metadata": {},
      "paid_at": 1790790412,
      "livemode": true,
      "created": 1790790400
    }
  ]
}

Retrieve an order

GET /v1/orders/{id}
curl https://api.vipter.com/v1/orders/ord_7b3e9f1c2a8d4e6f \
  -H "Authorization: Bearer vk_live_…"

Returns the order object.

The order object

FieldTypeContent
idtextord_…
objecttext"order"
statustextSee Order status.
paidbooleantrue when the money came in, even if it was later refunded in whole or in part. Use status for the detail.
status_sourcetextprovider when the status came from the payment provider; manual when someone set the status in the dashboard.
billing_reasontextWhy the order exists: purchase (one-off purchase), subscription_create (first charge of a subscription), subscription_cycle (renewal), manual (one-off charge on a subscription, through the dashboard or the API), usage (metered usage charge, at the close of a period).
customer, customer_emailtext or nullThe buyer.
subscriptiontext or nullsub_… when the order belongs to a subscription.
offertext or nullofr_… of the order's main offer.
order_typetext or nullHow the order was born: checkout, renewal, api (charge started by the store owner), trial_setup or card_setup (card registration without a charge). May gain new values.
recurrencetext or nullinitial, subsequent or unscheduled on subscription orders; null on one-off purchases.
currencytextThe order's currency.
amount_totalintegerThe total charged, in the minor unit, with discount, shipping and interest already applied.
amount_refundedintegerHow much was refunded so far.
amount_discountintegerThe coupon discount.
amount_shippingintegerShipping, on physical products.
amount_interestintegerInstallment interest passed on to the buyer.
installmentsinteger or nullIn how many installments it was paid.
payment_methodtext or nullcredit_card, debit_card, pix, boleto or wallet.
providertext or nullThe provider that processed it, such as pagarme, stripe, mercadopago or asaas.
coupon_codeslist of textThe coupons applied.
lineslistThe order's items. See Order lines.
shippingobject or nullOnly on orders with delivery: delivery status, carrier, tracking_code, tracking_url and address, in the same format as the customer's address.
external_order_idtext or nullAn identifier of yours, when the order came with one. On the orders of a subscription charge, the charge's sch_….
checkout_sessiontext or nullcs_… of the checkout session that produced the order. Only on the order paid through the session: the subscription's renewals come with null, and you link them to your system through subscription.
client_reference_idtext or nullYour identifier, copied from the checkout session's client_reference_id.
metadataobjectThe checkout session's metadata. {} on other orders.
paid_atinteger or nullWhen the payment was confirmed.
livemodebooleanfalse when the payment went through a provider's test connection.
createdintegerWhen the order was created.

Order status

statuspaidMeaning
pendingfalseAwaiting payment: PIX generated and unpaid, boleto issued, card under review.
pre_authorizedfalseAmount reserved on the card, not yet captured.
authorizedtruePaid.
failedfalseThe payment was refused or expired.
canceledfalseCanceled before being paid.
refund_pendingtrueRefund requested and not yet confirmed by the provider.
partially_refundedtruePart of the amount was returned. amount_refunded says how much.
refundedtrueThe whole amount was returned.
charged_backtrueThe buyer disputed the charge with their bank.

Order lines

Each item in lines:

FieldTypeContent
descriptiontext or nullThe item's name as it appeared in the checkout.
quantityintegerQuantity.
unit_amountinteger or nullUnit price, in the minor unit.
amountinteger or nullunit_amount times quantity.
offer, producttext or nullThe item's ofr_… and prd_….
kindtextThe item's role in the order: main (the main item), bump (an add-on offer ticked at checkout), composition (a line the team composed on a quick link) or charge (a line of a subscription charge).

Offers

An offer is what the buyer can pay for: a product with a price, a billing cycle and conditions. It is the equivalent of Stripe's price. Each offer has a ready checkout link.

List offers

GET /v1/offers
ParameterTypeContent
producttextOnly this product's offers (prd_…).
activetrue or falseOnly active offers, or only the ones that are not active.
typetextone_time or recurring.
limit, starting_after, ending_beforePagination. Offers come in alphabetical order by name.
curl "https://api.vipter.com/v1/offers?active=true&type=recurring" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/offers",
  "has_more": false,
  "data": [
    {
      "id": "ofr_6e2b8d4f1a9c3e7b",
      "object": "offer",
      "name": "Plano Pro mensal",
      "slug": "pro-mensal",
      "product": { "id": "prd_1a5c9e3b7d2f6a8c", "name": "Plano Pro" },
      "type": "recurring",
      "billing_cycle": "monthly",
      "custom_billing_days": null,
      "cycle_limit": null,
      "trial_days": 7,
      "setup_charge": false,
      "status": "active",
      "active": true,
      "prices": [
        { "id": "prc_2d8f4a6c1e9b3d7f", "currency": "brl", "unit_amount": 9900, "first_charge_amount": null, "default": true },
        { "id": "prc_7a1c3e5b9d2f4a6c", "currency": "usd", "unit_amount": 1900, "first_charge_amount": null, "default": false }
      ],
      "checkout_url": "https://pay.vipter.com/pro-mensal",
      "livemode": true,
      "created": 1788300000
    }
  ]
}

Retrieve an offer

GET /v1/offers/{id}
curl https://api.vipter.com/v1/offers/ofr_6e2b8d4f1a9c3e7b \
  -H "Authorization: Bearer vk_live_…"

Returns the offer object.

The offer object

FieldTypeContent
idtextofr_…
objecttext"offer"
nametextThe offer's name.
slugtext or nullThe checkout link's slug, when the offer has one.
productobjectThe product's id (prd_…) and name.
typetextone_time (one-off purchase) or recurring (subscription).
billing_cycletext or nullThe billing interval on recurring offers: daily, biweekly, monthly, quarterly, half_yearly, yearly or custom. null on one-off ones.
custom_billing_daysinteger or nullWith billing_cycle custom, the interval in days.
cycle_limitinteger or nullHow many charges the subscription makes in total. null means no limit.
trial_daysinteger or nullFree trial days. null when the offer has no trial.
setup_chargebooleantrue when the first charge has a different amount from the rest (first_charge_amount in prices).
statustextThe status in the catalog, such as active or inactive.
activebooleantrue when status is active. Only active offers accept purchases.
priceslistOne price per currency: id, currency, unit_amount, first_charge_amount (the first charge's amount when it differs, else null) and default (the currency the checkout uses when the buyer does not choose).
checkout_urltext or nullThe offer's checkout link, on the store's custom domain when there is one. null when the link is turned off in the offer's settings. Accepts the URL parameters.
livemodebooleantrue in production.
createdinteger or nullWhen the offer was created.

Products

A product groups offers: "Pro plan" is the product, "Pro plan monthly" and "Pro plan yearly" are its offers.

List products

GET /v1/products
ParameterTypeContent
activetrue or falseOnly active products, or only the ones that are not active.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/products?active=true" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/products",
  "has_more": false,
  "data": [
    {
      "id": "prd_1a5c9e3b7d2f6a8c",
      "object": "product",
      "name": "Plano Pro",
      "description": "Acesso completo à plataforma.",
      "type": "digital",
      "status": "active",
      "active": true,
      "product_family": null,
      "metadata": {},
      "livemode": true,
      "created": 1788200000
    }
  ]
}

Retrieve a product

GET /v1/products/{id}
curl https://api.vipter.com/v1/products/prd_1a5c9e3b7d2f6a8c \
  -H "Authorization: Bearer vk_live_…"

Returns the product object.

The product object

FieldTypeContent
idtextprd_…
objecttext"product"
nametextThe product's name.
descriptiontext or nullDescription.
typetext or nullThe product type, such as digital or physical. May gain new values.
statustextThe status in the catalog, such as active or inactive.
activebooleantrue when the product is active and was not deleted.
product_familytext or nullThe product family's ID, when the product belongs to one.
metadataobjectFree data stored on the product.
livemodebooleantrue in production.
createdinteger or nullWhen the product was created.

Checkout sessions

A checkout session is a checkout opened by your system for one offer, with the buyer, your reference and the destination after payment already set. The response carries the url where you send the buyer. When they pay, the session starts pointing to customer, order and subscription, and the checkout.session.completed event goes out in the 2026-11-01 catalog. It is the equivalent of Stripe's Checkout Session. The step by step with code is in SaaS: from sign-up to dashboard.

The session fixes the offer, the pack, the currency and the coupon: the buyer cannot change any of them on the page. With customer or customer_email, the email field comes filled in and locked. Creating the session creates nothing at the payment provider; that happens when the buyer fills in the form, as on a regular checkout link.

Create a session

POST /v1/checkout/sessions

Requires the write scope. Send an Idempotency-Key so you can repeat the call after a network error without creating two sessions.

ParameterTypeContent
offertextRequired, unless line_items is sent. The offer: ofr_… or the checkout link's slug. An offer that does not exist or belongs to another store gets 404 resource_missing with param offer.
line_items[0][price], line_items[0][quantity]text, integerAlias in Stripe's format: price is the offer and quantity is the pack. One item only. When offer or pack is also sent, they take precedence.
packintegerThe pack, in units (1 to 999). The offer must have a pack with that quantity, otherwise 400 pack_unavailable.
currencytextThe session's currency, ISO 4217 (brl, usd). It must exist in the offer's prices, otherwise 400 currency_unsupported, with the available currencies in message. Without it, the checkout opens in the offer's default currency and the buyer can switch, as on a regular link.
customertextcust_… of a store customer. Their email comes filled in and locked; name, phone and document come filled in. An ID that does not exist gets 404 resource_missing with param customer.
customer_emailtextThe buyer's email, when they are not a customer yet. The field comes filled in and locked: the checkout only accepts paying with that email. Stored in lowercase.
customer_nametextName to fill in the form, 2 to 120 characters. The buyer can change it.
client_reference_idtextYour identifier, up to 200 characters: the user's ID in your system. It is copied to the order and to the subscription, and filters the session list.
metadataobjectUp to 50 keys; keys up to 40 characters, values up to 500. Numbers and booleans are stored as text; null removes the key. It stays on the session and is copied to the order.
subscription_data[metadata]objectThe metadata of the subscription the session creates, on recurring offers. Without it, the subscription receives the session's metadata. Same limits.
discounts[0][coupon]textThe code of an active coupon of the store, case-insensitive. It comes applied at checkout. A non-existent or inactive coupon gets 400 coupon_invalid. One coupon only.
success_urltextWhere the buyer goes after paying. https://, up to 2000 characters (http://localhost is accepted in development). The text {CHECKOUT_SESSION_ID} is replaced with the session's id. See After the payment.
cancel_urltextBecomes the back link at the top of the checkout. It is also where the buyer goes if they open the session after it expired. Same format rules.
redirect_delayintegerSeconds the Vipter thank-you page stays on screen before going to success_url: 0 to 30, default 5.
expires_atintegerWhen the session expires, in Unix seconds: between 30 minutes and 24 hours from now, otherwise 400 parameter_invalid. Default: 24 hours.
localetextThe buyer's language, stored on the session and returned in the object: en, pt, es, fr, de, it, ja, ko, ru or zh.
modetextpayment or subscription. Optional: Vipter derives it from the offer's type and returns it in the object. A value different from the offer's type gets 400 mode_mismatch.
allow_promotion_codesbooleanAccepted for Stripe compatibility and ignored for now.
curl -X POST https://api.vipter.com/v1/checkout/sessions \
  -H "Authorization: Bearer vk_live_…" \
  -H "Idempotency-Key: 9c1f0a52-7e4b-4d3a-9b8e-2f6c1d0a7e45" \
  -H "Content-Type: application/json" \
  -d '{
    "offer": "ofr_6e2b8d4f1a9c3e7b",
    "customer_email": "ana@example.com",
    "client_reference_id": "user_8213",
    "metadata": { "plan": "pro" },
    "success_url": "https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://app.example.com/billing"
  }'

The response is 201 with the checkout.session object:

{
  "id": "cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
  "object": "checkout.session",
  "status": "open",
  "payment_status": "unpaid",
  "url": "https://pay.vipter.com/c/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b",
  "mode": "subscription",
  "offer": { "id": "ofr_6e2b8d4f1a9c3e7b", "name": "Plano Pro mensal" },
  "pack": null,
  "currency": null,
  "amount_total": 9900,
  "customer": null,
  "customer_email": "ana@example.com",
  "customer_name": null,
  "client_reference_id": "user_8213",
  "metadata": { "plan": "pro" },
  "subscription_data": { "metadata": {} },
  "discounts": [],
  "success_url": "https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}",
  "cancel_url": "https://app.example.com/billing",
  "redirect_delay": 5,
  "locale": null,
  "order": null,
  "subscription": null,
  "expires_at": 1790272800,
  "completed_at": null,
  "livemode": true,
  "created": 1790186400
}

Send the buyer to url. The address lives at https://pay.vipter.com/c/{id} or, when the store has an active custom domain, on that domain.

Errors of this call, besides the general errors:

codeHTTPMeaning
resource_missing404The offer (param offer) or the customer (param customer) does not exist in the store.
offer_unavailable400The offer exists but cannot be sold right now: checkout link turned off, archived offer, no price, or the store is not in a condition to sell. message says why.
pack_unavailable400The offer has no pack with the quantity requested in pack.
currency_unsupported400The offer has no price in the requested currency.
mode_mismatch400mode does not match the offer's type.
coupon_invalid400The coupon does not exist or is inactive. param is discounts[0].coupon.
selling_blocked403The store cannot sell because its Vipter subscription is past due. The type is permission_error.

Retrieve a session

GET /v1/checkout/sessions/{id}
curl https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b \
  -H "Authorization: Bearer vk_live_…"

Returns the checkout.session object. It is the call your success page makes with the id that arrived in the success_url: check status and payment_status instead of trusting the redirect alone. After payment, customer, order and, on recurring offers, subscription come filled in. subscription may arrive a few seconds after order, when the provider's notice is processed; if it still comes null, query again or wait for the webhook.

List sessions

GET /v1/checkout/sessions
ParameterTypeContent
customertextOnly this customer's sessions (cust_…), including the ones that gained the customer when paid.
client_reference_idtextOnly sessions created with this client_reference_id, exact match.
statustextopen, complete or expired.
payment_statustextunpaid, paid or pending.
limit, starting_after, ending_beforePagination.
curl "https://api.vipter.com/v1/checkout/sessions?client_reference_id=user_8213&status=complete" \
  -H "Authorization: Bearer vk_live_…"

Returns a list object of checkout.session, newest first.

Expire a session

POST /v1/checkout/sessions/{id}/expire

Closes an open session before its deadline: a buyer who opens the url afterwards goes to cancel_url, or sees an unavailable-link page. Useful when the user gave up in your system or chose another plan. Requires the write scope.

curl -X POST https://api.vipter.com/v1/checkout/sessions/cs_4b7e2d9a1c3f5e8b0d2a6c4e1f3b/expire \
  -H "Authorization: Bearer vk_live_…"

Returns the object with status: "expired" and generates the checkout.session.expired event. A session that is already complete or expired gets 400 checkout_session_not_open, with the current status in message.

The checkout.session object

FieldTypeContent
idtextcs_…
objecttext"checkout.session"
statustextopen, complete or expired. See Session status.
payment_statustextunpaid, paid or pending.
urltext or nullThe checkout address. Only while status is open; afterwards it comes null.
modetext or nullpayment on a one-off offer, subscription on a recurring one.
offerobjectThe offer's id (ofr_…) and name.
packinteger or nullThe fixed pack, in units.
currencytext or nullThe currency fixed at creation. null when the session let the buyer choose.
amount_totalinteger or nullThe offer's price in the session's currency (or the default currency), in the minor unit: the first charge's amount, when it differs. It comes before coupon, shipping and add-ons, and null on sessions with a pack. The amount actually charged is in the order's amount_total.
customertext or nullcust_…: the one you passed, or the customer created when the buyer paid.
customer_email, customer_nametext or nullWhat you passed; with customer, the customer's email and name.
client_reference_idtext or nullYour identifier.
metadataobjectWhat you sent.
subscription_dataobjectmetadata: what you sent in subscription_data[metadata], or {}.
discountslist[{ "coupon": "CODE" }] when the session has a coupon; otherwise [].
success_url, cancel_urltext or nullAs you sent them, with {CHECKOUT_SESSION_ID} still to be replaced.
redirect_delayintegerSeconds before the redirect, 0 to 30.
localetext or nullThe language sent.
ordertext or nullord_… of the order the session produced. Filled in when the buyer pays or generates a PIX.
subscriptiontext or nullsub_… of the subscription created, on recurring offers.
expires_atintegerWhen the session expires.
completed_atinteger or nullWhen the session became complete.
livemodebooleantrue in production.
createdintegerWhen the session was created.

Session status

statuspayment_statusMeaning
openunpaidThe buyer has not paid yet. A declined card leaves the session open: they can try again on the same page.
completepaidPaid: approved card, or paid PIX. order is filled in.
completependingThe buyer generated a PIX, or the card went under review, and the money has not arrived yet. order is filled in with status: "pending". The session stays like this until the payment is confirmed (paid) or fails (unpaid).
completeunpaidThe pending payment did not happen: the PIX expired or the charge was refused after review. The session does not reopen; create another one.
expiredunpaidThe deadline passed without payment, or someone called POST …/expire.

Each change generates an event of the 2026-11-01 catalog: checkout.session.completed when the session becomes complete (with paid or pending), checkout.session.async_payment_succeeded when a pending payment is confirmed, checkout.session.async_payment_failed when it fails, and checkout.session.expired. A session completes only once.

After the payment

  1. The buyer pays on the Vipter page and sees the store's thank-you page.
  2. Once the payment is confirmed, the page counts redirect_delay seconds and goes to success_url, with {CHECKOUT_SESSION_ID} replaced with the session's id. While a PIX is unpaid, the page keeps waiting and does not redirect.
  3. The session's success_url takes precedence over the success URL of the quick link, the offer's and the store's default. Without success_url, the next one on the list applies.

When the product has downloadable files, the page does not redirect on its own: it shows the files and a button to continue to the success_url.

A buyer who opens the url of an already paid session is taken to the order's thank-you page. An expired session leads to cancel_url or, without it, to an unavailable-link page. Overdue sessions are marked expired by a periodic routine, and also right away if someone opens the link.

Customer portal

Create a portal session

POST /v1/billing_portal/sessions

Generates a direct sign-in link to the store's customer portal for one customer, without the e-mail code step. It serves the "manage subscription" button of your system: the customer changes the card, cancels or sees the charges in the Vipter portal. Requires the write scope.

ParameterTypeContent
customertextRequired. The customer's cust_….
return_urltextAccepted for Stripe compatibility and returned in the object. The portal does not have a button back to it yet.
curl -X POST https://api.vipter.com/v1/billing_portal/sessions \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "customer": "cust_9d2e4f6a8b1c3d5e" }'
{
  "id": "bps_3e9a1c7f5b2d4e6a8c0b",
  "object": "billing_portal.session",
  "url": "https://portal.vipter.com/loja-demo/verify?token=EXAMPLE-TOKEN",
  "customer": "cust_9d2e4f6a8b1c3d5e",
  "return_url": null,
  "expires_at": 1790187300,
  "livemode": true,
  "created": 1790186400
}
FieldTypeContent
idtextbps_…. The session cannot be retrieved later.
objecttext"billing_portal.session"
urltextThe sign-in link. It is valid for 15 minutes and serves one sign-in: when opened, it creates the portal session in the customer's browser and stops working. Generate a new link on every click, at the time of the click, and redirect the customer to it; do not store it or send it by e-mail.
customertextThe customer.
return_urltext or nullWhat you sent.
expires_atintegerWhen the link stops being valid.
livemodebooleantrue in production.
createdintegerWhen the session was created.

The link opens at the store's portal address: the custom domain, when there is an active one, or portal.vipter.com/{slug}.

codeHTTPMeaning
resource_missing404The customer does not exist in the store.
portal_disabled400The customer portal is turned off in the store's settings. See Customer portal.
portal_unavailable400The store has no portal address yet: it needs a slug or an active custom domain.

Events

Every event Vipter generates is stored and can be queried through the API, in both catalogs: to check what your endpoint received, to recover what it missed while it was disabled, and to request a resend in code. The body of each event is the same envelope that reaches the endpoint, and api_version says which catalog it belongs to. Test events are not listed.

List events

GET /v1/events
ParameterTypeContent
typestringAn exact type, such as invoice.paid, or a pattern with *, such as invoice.* or customer.subscription.*.
created[gte], created[gt], created[lte], created[lt]integerOnly events created at or after, after, at or before, or before that moment, in Unix seconds.
limit, starting_after, ending_beforePagination. The cursor is an event id.
curl "https://api.vipter.com/v1/events?type=invoice.*&created[gte]=1790726400" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/events",
  "has_more": false,
  "data": [
    {
      "id": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
      "object": "event",
      "type": "invoice.paid",
      "api_version": "2026-11-01",
      "created": 1790790413,
      "livemode": true,
      "pending_webhooks": 0,
      "request": { "id": null, "idempotency_key": null },
      "data": {
        "object": { "id": "ord_7b3e9f1c2a8d4e6f", "object": "order", "status": "authorized", "paid": true, "…": "…" }
      }
    }
  ]
}

The list comes newest first and merges both catalogs: the same fact appears twice when the store has endpoints on both versions, once under each name. An event of version 2026-09-01 comes without pending_webhooks and request, as in that version's envelope.

Retrieve an event

GET /v1/events/{id}
curl https://api.vipter.com/v1/events/evt_5a7c9e1b3d5f7a9c1e3b5d7f \
  -H "Authorization: Bearer vk_live_…"

Returns the event object, the same JSON the endpoint received in the POST. In version 2026-11-01, pending_webhooks is computed at query time: how many deliveries of the event have not succeeded yet. A test event can be retrieved by the id the test call returns. An id that does not exist in the store gets 404 resource_missing.

Resend an event

POST /v1/events/{id}/resend

Queues the event for delivery again, with the same id and the same created, and sends it right away. Requires the write scope.

ParameterTypeContent
webhook_endpointstringThe id of an endpoint. With it, only that endpoint receives the event: its delivery goes back to pending with the attempts reset, or is created if the endpoint never had one, such as an endpoint created after the event or disabled when it went out. Without it, every delivery the event already has is resent, including the ones that had succeeded.
curl -X POST https://api.vipter.com/v1/events/evt_5a7c9e1b3d5f7a9c1e3b5d7f/resend \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a" }'
{
  "object": "event_resend",
  "event": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
  "webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
  "deliveries": 1
}

deliveries is how many deliveries were queued. Without webhook_endpoint, it is the number of endpoints that already had a delivery of the event; 0 when none had. The endpoint must receive the event's catalog: an invoice.paid event (2026-11-01) cannot be sent to a 2026-09-01 endpoint. Resending to a disabled endpoint closes the delivery with endpoint disabled; enable it first. If the delivery fails, it follows the retry schedule from the start. Your idempotency check treats the resend as a repeat.

codeHTTPMeaning
resource_missing404The event, or the endpoint in webhook_endpoint, does not exist in the store.
invalid_event_type400The endpoint receives another catalog. The message says which one the event belongs to and which one the endpoint receives.

Webhook endpoints

The same endpoints as the GeneralIntegrationsAutomationsWebhooks page of the dashboard, created and managed in code. An endpoint created through the API shows up in the dashboard like any other, and the rules are those of Receive events in your system: an https:// URL, one catalog per endpoint, retries and automatic disabling. An endpoint's id is a UUID; treat it as an opaque string.

List endpoints

GET /v1/webhook_endpoints
curl https://api.vipter.com/v1/webhook_endpoints \
  -H "Authorization: Bearer vk_live_…"

Returns a list object with every endpoint of the store, oldest first, with no filters. The secret is not in the list.

Create an endpoint

POST /v1/webhook_endpoints

Requires the write scope. The response is 201 and carries the secret (whsec_…) only once: store it on your server to verify the signature. Afterwards, only rotating the secret produces another.

ParameterTypeContent
urlstringRequired. The https:// address of your server, up to 2000 characters, on a public host. A host on the internal network (localhost, 10.x, 192.168.x, .internal or .local names) gets 400 private_host; an address without https:// gets 400 invalid_url.
descriptionstringA reminder for the team, up to 200 characters.
enabled_eventslistThe types the endpoint receives, up to 100: names from the version's catalog, such as invoice.paid, or patterns with *, such as customer.subscription.*. Omitted, empty or ["*"], the endpoint receives every event of the catalog, including the ones created in the future. A name that does not exist in the version gets 400 invalid_event_type.
api_versionstringThe catalog the endpoint receives: 2026-11-01 (default) or 2026-09-01. It does not change after creation.
curl -X POST https://api.vipter.com/v1/webhook_endpoints \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example.com/webhooks/vipter",
    "description": "SaaS billing",
    "enabled_events": ["checkout.session.*", "customer.subscription.*", "invoice.paid"]
  }'
{
  "id": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
  "object": "webhook_endpoint",
  "url": "https://app.example.com/webhooks/vipter",
  "description": "SaaS billing",
  "enabled_events": ["checkout.session.*", "customer.subscription.*", "invoice.paid"],
  "api_version": "2026-11-01",
  "status": "enabled",
  "disabled_reason": null,
  "consecutive_failures": 0,
  "created_via": "api",
  "secret": "whsec_EXAMPLE0000000000000000000000000000",
  "livemode": true,
  "created": 1790186400
}

The endpoint is born enabled and starts receiving the events created from then on; earlier events do not reach it, except through a resend with webhook_endpoint.

Retrieve an endpoint

GET /v1/webhook_endpoints/{id}
curl https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a \
  -H "Authorization: Bearer vk_live_…"

Returns the webhook_endpoint object, without secret. An id that does not exist in the store gets 404 resource_missing.

Update an endpoint

POST /v1/webhook_endpoints/{id}

Requires the write scope. Only the fields sent change.

ParameterTypeContent
urlstringThe new address, with the same rules as on creation. The following deliveries already go to it.
descriptionstring or nullnull clears the description.
enabled_eventslistReplaces the whole list. ["*"] goes back to receiving every event of the catalog. In the dashboard this list cannot be changed; through the API, it can.
statusstringenabled or disabled. It is the dashboard's Active switch: enabled resets consecutive_failures and clears disabled_reason, which turns an automatically disabled endpoint back on. While it is disabled, new events are not stored for it.

An api_version in the body is ignored: the version does not change. To move to the other catalog, create a new endpoint and remove the old one.

curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a \
  -H "Authorization: Bearer vk_live_…" \
  -H "Content-Type: application/json" \
  -d '{ "enabled_events": ["*"], "status": "enabled" }'

Returns the updated webhook_endpoint object.

Delete an endpoint

DELETE /v1/webhook_endpoints/{id}

Deletes the endpoint, its secret and its delivery history; pending deliveries stop. Requires the write scope.

{ "id": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a", "object": "webhook_endpoint", "deleted": true }

Rotate the secret

POST /v1/webhook_endpoints/{id}/rotate_secret

Generates a new secret and returns the webhook_endpoint object with it in secret, only once. The previous secret keeps signing for 24 hours: during that period, each delivery carries two v1= values in the Vipter-Signature header, and your server can switch the environment variable without losing events. See Rotate the secret. Requires the write scope.

curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/rotate_secret \
  -H "Authorization: Bearer vk_live_…"

Send a test event

POST /v1/webhook_endpoints/{id}/test

Creates a test customer.created event and delivers it to this endpoint only, in its catalog: a customer object in the format of the endpoint's version, with an extra "test": true and livemode: false. It is the same as the Send test event button on the endpoint's card in the dashboard. The event does not go through automations, e-mails, invoices, member areas or pixels, does not reach the other endpoints and does not appear in GET /v1/events. The format is in the catalog. Requires the write scope.

curl -X POST https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/test \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "webhook_test",
  "webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
  "event": "evt_1c3e5a7b9d0f2e4c6a8b0d2f"
}

The delivery goes out right away. Follow it in GET /v1/webhook_endpoints/{id}/deliveries or under Recent deliveries in the dashboard. If your server fails, it follows the retry schedule of a real event and counts toward automatic disabling. With the endpoint disabled, the delivery ends right away with endpoint disabled.

List an endpoint's deliveries

GET /v1/webhook_endpoints/{id}/deliveries
ParameterTypeContent
statusstringpending, delivering, succeeded, failed or exhausted.
limit, starting_after, ending_beforePagination. The cursor is a delivery id.
curl "https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/deliveries?status=failed" \
  -H "Authorization: Bearer vk_live_…"
{
  "object": "list",
  "url": "https://api.vipter.com/v1/webhook_endpoints/0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a/deliveries",
  "has_more": false,
  "data": [
    {
      "id": "6f1d2c3b-4a5e-4f6a-9b8c-7d6e5f4a3b2c",
      "object": "webhook_delivery",
      "event": "evt_5a7c9e1b3d5f7a9c1e3b5d7f",
      "webhook_endpoint": "0b7e2c4a-9d1f-4e3b-8a6c-5f2d1e0c9b8a",
      "status": "failed",
      "attempts": 2,
      "next_attempt_at": 1790790780,
      "last_attempt_at": 1790790480,
      "delivered_at": null,
      "last_response_status": 500,
      "last_error": "HTTP 500",
      "last_duration_ms": 184,
      "created": 1790790413
    }
  ]
}

Returns a list object of webhook_delivery, newest first: one delivery per event the endpoint received. What each status means is in Retries, disabling and resending.

The endpoint object

FieldTypeContent
idstringThe endpoint's UUID.
objectstring"webhook_endpoint"
urlstringThe address that receives the events.
descriptionstring or nullThe description.
enabled_eventslistThe types and patterns the endpoint receives. ["*"] when it receives every event of the catalog.
api_versionstring2026-11-01 or 2026-09-01: the catalog the endpoint receives.
statusstringenabled or disabled.
disabled_reasonstring or nullThe reason, when Vipter disabled the endpoint on its own: auto-disabled after 20 exhausted deliveries. null otherwise, including when the team turned the switch off.
consecutive_failuresintegerConsecutive deliveries that exhausted their attempts. At 20, the endpoint is disabled; a succeeded delivery resets it.
created_viastringdashboard or api: where the endpoint was created.
secretstringThe signing secret, whsec_…. Only in the response of create and rotate the secret.
livemodebooleantrue in production.
createdintegerWhen the endpoint was created.

The delivery object

FieldTypeContent
idstringThe delivery's UUID.
objectstring"webhook_delivery"
eventstringevt_… of the delivered event. Retrieve it with GET /v1/events/{id}.
webhook_endpointstringThe endpoint.
statusstringpending (queued), delivering (being sent), succeeded (your server answered 2xx), failed (the last attempt failed and another is scheduled) or exhausted (the attempts ran out, or the endpoint was disabled).
attemptsintegerHow many attempts were made, out of up to 8. Goes back to 0 on a resend.
next_attempt_atinteger or nullWhen the next attempt is scheduled. Only with status pending or failed.
last_attempt_atinteger or nullWhen the last attempt was.
delivered_atinteger or nullWhen your server answered 2xx.
last_response_statusinteger or nullThe HTTP code of the last response. null when there was no response.
last_errorstring or nullThe error of the last attempt: HTTP 500, timeout, endpoint disabled or the network message. null when it succeeded.
last_duration_msinteger or nullHow long the last attempt took, in milliseconds.
createdintegerWhen the delivery was created.

Coming soon

What does not exist in the API yet: a client_reference_id filter on GET /v1/subscriptions and on GET /v1/customers. To find one of your users' subscription, use the subscription field of the checkout session that created it, or store the sub_… that arrives in the webhook. Metered usage, previously listed here, is live: see Metered usage.

What to do next

Was this page helpful?

On this page

Language