Skip to main content
Webhooks — Vita Wallet Business API
Webhooks are the recommended way to receive real-time notifications from Vita about events in your account — transaction status changes, deposits, withdrawals, exchanges, and many more. Instead of polling the API to check for updates, your server receives an HTTP POST notification whenever something relevant happens. This module gives you full control over:

Where to receive

Configure your own webhook URL.

Which events

Select from the available transaction categories.

Delivery monitoring

Inspect recent events, delivery attempts, and your server’s responses.

Available endpoints

Authenticating requests to these endpoints

Two distinct signatures. This section covers the signature used when you call Vita’s webhook configuration endpoints. It is not the same as the signature used to verify webhooks Vita sends to you — that one is covered below in Webhook signature calculation.
Every request to GET /api/businesses/webhooks, PUT /api/businesses/webhooks, and GET /api/businesses/events requires the following headers: Requests without these headers, or signed with the webhook-verification formula, will fail:
  • No headers at all → 401, generic error.
  • Correct headers but wrong signature formula → 422, "Invalid signature".
A ready-to-import Postman collection with correctly authenticated example requests is available alongside this documentation — use it instead of hand-building the examples below if you just want to test the endpoints quickly.

Webhook signature calculation

Vita signs every webhook notification it sends to your server using an HMAC-SHA256 signature. The signature lets you verify that the request truly comes from Vita Business and that its content hasn’t been modified in transit. It’s calculated using your business_secret and is included in the Authorization header alongside a timestamp in the X-Date header. Your server must reconstruct and validate this signature on every webhook delivery before processing the payload.
Security-critical. Never skip signature validation. An unverified webhook should be considered untrusted and rejected.

Required credentials

Signature calculation process

Formula
1

Obtain x_login

The hexadecimal hash provided by Vita Business.
2

Generate x_date

ISO 8601 timestamp with timezone.
3

Build the data hash

Use the event structure (see below).
4

Convert to compact JSON

No spaces, no line breaks.
5

Concatenate directly

x_login + x_date + compact_json — no separators.
6

Calculate HMAC-SHA256

Using your business_secret as the key.
7

Send the signature

In the Authorization header.

Data hash structure

The data hash contains the following top-level fields:
About event_id. Despite its name and the UUID-like appearance in examples, this is an opaque text identifier, not a validated UUID. Most real values carry an extra numeric suffix (e.g. f3065db9-1a97-493a-a8c2-97cbb84bf889-24544). Do not validate its format — treat it as an opaque string.

Worked example

Inputs
  • x_login: f2fe62688115b98e060533145ca6c32ec070bb9e
  • x_date: 2026-01-15T14:27:47+00:00
  • business_secret: [your_secret_key]
Data hash (regenerated from a real event, including previously undocumented fields)
Step 1 — Convert the data hash to compact JSON. No spaces, no line breaks. Vita always sends the body already compact; those are the exact bytes that were signed. Do not reformat, reorder, or re-serialize the body before validating — any change invalidates the signature. Step 2 — Build the string to sign. Direct concatenation, no separators:
Step 3 — Calculate HMAC-SHA256. Key: business_secret. Message: the full string from step 2. Output: a 64-character hexadecimal signature. Step 4 — Final HTTP request headers:
Body: the exact JSON object used to build the signature, as sent by Vita (always compact — this is not a stylistic choice on your end).

How to validate an incoming signature

1

Extract x_date

From the X-Date header.
2

Use your x_login

Stored on your side.
3

Take the received JSON body exactly as-is

Do not modify, reformat, reorder, or re-serialize it.
4

Reconstruct string_to_sign

x_login + x_date + body_json.
5

Calculate the expected signature

HMAC-SHA256 with your business_secret.
6

Compare

With the one received in the Authorization header (after V2-HMAC-SHA256, Signature:).
7

Accept or reject

If the signatures match, the webhook is authentic. If they don’t, reject the request.

Important notes

  • x_login is a hexadecimal hash (40 characters in the example above).
  • The JSON of the data hash is always compact — no spaces, no line breaks.
  • x_date must be ISO 8601 with timezone (e.g., 2026-01-15T14:27:47+00:00).
  • Concatenation is direct, with no separators: x_login + x_date + json_string.
  • Authorization header format: V2-HMAC-SHA256, Signature: {signature}.
  • The x_date used in the signature must match exactly the X-Date header sent in the request.
  • The resulting signature is a 64-character hexadecimal hash.
  • Use the body as received — never re-serialize the JSON before validating, as it may change byte-for-byte.

API status reference

This reference describes all statuses used across Vita, organized by the entity they apply to:
  • Payment Orders — used in Payins for collecting funds.
  • Payment Order Attempts — individual payment trials within a Payment Order.
  • Transactions — internal fund movements used in both Payins and Payouts.
Understanding these statuses is essential for correctly interpreting webhook events, building dashboards, and implementing reconciliation flows.

Payins

Payment Order

A Payment Order is a payment request created to collect funds from a customer. It represents the entire payment lifecycle and can contain one or more attempts. Each Payment Order has:
  • A unique identifier (public_code or issue).
  • An expiration date.
  • A final status reflecting the overall outcome of the collection process.
Who can cancel a Payment Order. You (the client) cannot cancel a Payment Order through the API — there is no cancel operation exposed. However, Vita can cancel a Payment Order internally for operational or compliance reasons. When that happens, you receive a payment_order.cancelled webhook and the order’s final status becomes cancelled. Build your integration to handle this event and status explicitly — do not treat cancelled as an impossible or invalid state.

Payment Order Attempt

A Payment Order Attempt represents a single trial by the user to fulfill a Payment Order. Since users may run into issues during checkout — declined cards, insufficient funds, closed browser — each session or transaction try is logged as a separate Attempt. This structure gives you a granular history: while several attempts might fail or be cancelled, the parent Payment Order remains valid until one attempt succeeds, the order is cancelled by Vita, or the overall time limit elapses. Understanding the relationship
  • A single Payment Order can contain multiple (N) Payment Order Attempts.
  • A payment_order_attempt.cancelled event does not mean the payment link is cancelled — it only means that specific attempt failed.
  • A user may start a new attempt after a failure, which could eventually move the Payment Order to paid.
When to consider an order PAID An order is successfully settled when either of the following webhooks is received (matching the public_code or issue of the order):
  • payment_order.paid
  • payment_order_attempt.paid
When to consider an order a terminal failure An order reaches a terminal, unsuccessful state when either of the following is received:
  • payment_order.expired — the link’s time limit elapsed without payment.
  • payment_order.cancelled — Vita cancelled the order internally.

Payment Order and Payment Order Attempt event payloads

Subscription requirement. payment_order.* and payment_order_attempt.* events are only sent if you are subscribed to the payment category. Being subscribed to deposit or other categories does not deliver these events. If you run a Payins integration based on payment links, make sure payment is included in your categories list — otherwise you will silently receive none of these notifications.
These payloads carry payer and payment-specific data (payer name, document, amount, currency, on-hold-related fields, spread, fixed cost, country code, and more) rather than the transaction-category attributes described further below. Match incoming events to your internal records using public_code or issue.
A full field-by-field reference for payment_order.* and payment_order_attempt.* payloads is pending — request the current schema from your integration contact if you need it before it’s published here.

Payment Order statuses

Payment Order Attempt statuses

Payment Order Attempts use the same statuses as Payment Orders, applied to the specific attempt:
A cancelled attempt does not cancel the parent Payment Order. A new attempt may still be initiated.

Transactions

Transactions track the internal movement of funds within Vita and are used in both Payins and Payouts (withdrawals, Vita Sent, crypto payouts, exchanges, etc.).

Transaction statuses

Status lifecycles at a glance

Payment Order lifecycle Transaction lifecycle

Important considerations

All status changes are communicated via Webhooks. Configure your webhook URL and category subscriptions to ensure you receive real-time updates.
For Payment Orders, always match incoming webhook events to your internal records using public_code or issue. Both payment_order.* and payment_order_attempt.* events relate to the same underlying order — and both require the payment category subscription.
This is one of the most common misinterpretations. A payment_order_attempt.cancelled event affects only that attempt — the parent order remains valid until it’s paid, expired, or cancelled by Vita.
  • Final (terminal): paid, expired, cancelled, reimbursed, completed, denied, failed, time_out.
  • Intermediate: pending, checking, processed, started, on_hold, reimbursing.
Plan your integration logic to wait for a final status before considering an operation truly resolved.