# Core concepts

> The object model, event system, and three-product architecture behind every VINR integration.

Every VINR integration — whether you're taking a one-off payment or running a subscription-plus-loyalty loop — is built on the same small set of ideas. This page explains them once so the rest of the documentation makes sense.

## Three products, one platform

VINR exposes three products through a single API and a single SDK. You can use any one of them independently, or combine all three.

[Payments](/docs/payments) — One-time and saved-method charges, refunds, disputes, and payouts. The foundation everything else builds on.

[Billing](/docs/billing) — Products, prices, subscriptions, invoices, and revenue recognition. Handles the full recurring-revenue lifecycle.

[Engagement](/docs/engagement) — Loyalty accounts, points, rewards, and campaigns. Layers onto Payments and Billing or runs standalone.

The three products share one customer object. A `cust_` you create for a first payment is the same record you attach a subscription and a loyalty account to — no duplicate data, no re-collecting card details, no second identity for the same person.

> You do not need to use all three products. Many integrations start with Payments alone and add Billing or Engagement later. The `cust_` object is backward-compatible — you can attach a subscription to a customer who has only ever made one-off payments.

## Objects and IDs

Every resource in VINR is an object with a typed, prefixed ID. The prefix tells you exactly what you're looking at — in logs, in dashboard URLs, and in webhook payloads.

| Prefix   | Object                | Description                                                      |
| -------- | --------------------- | ---------------------------------------------------------------- |
| `cust_`  | Customer              | A person or business. Shared across all three products.          |
| `pay_`   | Payment               | A single charge attempt. May require 3DS before confirming.      |
| `pm_`    | Payment method        | A saved card or bank account attached to a customer.             |
| `re_`    | Refund                | A full or partial reversal of a `pay_`.                          |
| `dp_`    | Dispute               | A chargeback raised by the cardholder's bank.                    |
| `po_`    | Payout                | Funds settled to your bank account.                              |
| `prod_`  | Product               | A thing you sell. Has one or more `price_` objects.              |
| `price_` | Price                 | An amount, currency, and billing interval.                       |
| `sub_`   | Subscription          | A customer subscribed to a price. Drives the invoice lifecycle.  |
| `inv_`   | Invoice               | A billing statement. Paid automatically or sent to the customer. |
| `mbu_`   | Metered billing usage | A usage record for consumption-based pricing.                    |
| `loy_`   | Loyalty account       | A points balance attached to a `cust_`.                          |
| `ptx_`   | Points transaction    | An earn or burn event against a loyalty account.                 |
| `rwd_`   | Reward                | A redeemable item in a loyalty catalog.                          |
| `rdm_`   | Redemption            | A customer exchanging points for a reward.                       |
| `evt_`   | Event                 | A webhook event delivered to your endpoint.                      |
| `we_`    | Webhook endpoint      | A registered URL that receives `evt_` objects.                   |

> Object IDs are not portable across environments. A `cust_` created in sandbox does not exist in live. Re-create reference data when you move to production.

## The event model

VINR is event-driven. Every meaningful state change — a payment completing, an invoice generating, a customer earning points — emits an `evt_` object delivered to your registered webhook endpoint. **Do not poll the API for state changes; react to events instead.**

A webhook payload always has the same shape:

```typescript
{
  id: "evt_01j2k...",
  type: "payment.completed",
  created: 1717200000,
  data: { /* the full object that changed */ }
}
```

Your endpoint must verify the `x-vinr-signature` header before trusting the payload, then return `2xx` within 30 seconds. VINR retries unacknowledged deliveries with exponential backoff for up to 72 hours.

```typescript
import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

export async function POST(req: Request) {
  const payload = await req.text();
  const signature = req.headers.get('x-vinr-signature');

  const event = vinr.webhooks.verify(payload, signature);

  switch (event.type) {
    case 'payment.completed':
      await fulfillOrder(event.data.metadata.orderId);
      break;
    case 'invoice.paid':
      await grantAccess(event.data.customerId);
      break;
    case 'loyalty.points.earned':
      await notifyCustomer(event.data.customerId, event.data.amount);
      break;
  }

  return new Response('OK', { status: 200 });
}
```

Make every handler **idempotent**: store the processed `evt_` id and short-circuit duplicates. VINR may redeliver the same event on retry.

### Key events by product

| Event                     | When it fires                                       |
| ------------------------- | --------------------------------------------------- |
| `payment.completed`       | A charge was successfully captured                  |
| `payment.failed`          | A charge attempt was declined or errored            |
| `payment.refunded`        | A `re_` was applied to a `pay_`                     |
| `invoice.created`         | A billing cycle generated a new invoice             |
| `invoice.paid`            | An invoice was collected successfully               |
| `invoice.payment_failed`  | An invoice charge attempt failed (triggers dunning) |
| `subscription.deleted`    | A subscription was cancelled or expired             |
| `loyalty.points.earned`   | A `ptx_` earn was recorded                          |
| `loyalty.points.redeemed` | A customer redeemed points for a reward             |
| `payout.paid`             | Funds were settled to your bank account             |
| `dispute.created`         | A chargeback was opened against a `pay_`            |

## Environments

Every VINR account has two isolated environments. They share nothing — not keys, not objects, not money.

|              | Sandbox                        | Live                      |
| ------------ | ------------------------------ | ------------------------- |
| API base URL | `https://sandbox.api.vinr.com` | `https://api.vinr.com`    |
| Key prefix   | `sk_test_…` / `pk_test_…`      | `sk_live_…` / `pk_live_…` |
| Money        | Simulated — no real funds      | Real funds and payouts    |
| Test cards   | Required                       | Rejected                  |
| KYB required | No                             | Yes                       |

The SDK switches base URL automatically based on which key you provide — a `sk_test_…` key can never touch the live API. If you call the REST API directly, keep the host and key in sync; a live key against the sandbox host returns `401`.

> Switch environments using the toggle in the top-left of the dashboard. Each environment has its own API keys, webhook endpoints, and webhook secrets.

## Authentication

VINR uses two API keys per environment:

- **Secret key** (`sk_test_…` / `sk_live_…`) — server-side only. Used to create payments, read customer data, and verify webhooks. Never expose this in client-side code or commit it to version control.
- **Public key** (`pk_test_…` / `pk_live_…`) — safe for browser use. Initializes the embedded Elements UI. It cannot make server-side API calls.

Pass the secret key via the `X-Api-Key` header (REST) or the SDK constructor. See [Authentication](/docs/getting-started/authentication) for scoped keys, rotation, and key management best practices.

## Next steps

[Quick Start](/docs/getting-started/quick-start) — Make your first payment in five minutes using the concepts above.

[Integration overview](/docs/getting-started/integration-overview) — Choose your integration depth: hosted, embedded, or API-direct.

[Webhooks](/docs/integration/webhooks) — Signature verification, retries, and idempotent event handling in depth.

[Authentication](/docs/getting-started/authentication) — Scoped keys, rotation, and keeping credentials secure.
