Skip to content

Payments

Vidivo uses Stripe Connect Express with a time-window hold/capture model. Guests pay per minute; hosts receive payouts minus a 7% platform fee.

The billing model is designed to pre-authorize funds before they are spent, preventing unpayable debts at call end.

Time: 0 10 20 30 min
│ │ │ │
▼ ▼ ▼ ▼
Holds: [─W1─────][─W2─────][─W3────]
↑ ↑
W1 captured W2 captured
At call end (e.g. 24 min):
├── W3 partial: 4 min × rate = captured
└── Remainder of W3 hold: released

Step-by-step:

  1. Guest submits payment method. Stripe hold placed for Window 1 (window_size × rate).
  2. At window_size - 60 seconds, hold for Window 2 is placed.
  3. At the window_size boundary, Window 1 is captured (charged permanently).
  4. This repeats for every subsequent window.
  5. When the call ends:
    • The partial final window is captured: ⌈elapsed_minutes⌉ × rate
    • All remaining holds are released immediately.

Host rate: $3.00/min, window size: 10 minutes.

TimeActionStripe Operation
T+0sCall startsHold $30.00 (W1)
T+9m60s before W1 endsHold $30.00 (W2)
T+10mW1 boundaryCapture $30.00 (W1)
T+19m60s before W2 endsHold $30.00 (W3)
T+20mW2 boundaryCapture $30.00 (W2)
T+23m30sGuest ends callCapture $12.00 (4 min rounded up × $3), Release $18.00

Total charged: $72.00 | Host receives: $72.00 × 0.93 = $66.96

All payment mutations use Stripe Idempotency Keys to prevent duplicate charges. Always pass X-Idempotency-Key on:

  • POST /calls/initiate (first hold)
  • POST /calls/{id}/end (final capture)

Retrieve payment history for the authenticated user.

Authentication: Bearer token (host or guest).

Behavior:

  • Hosts see their earning records — amounts after platform fee deduction.
  • Guests see their charge records — what was captured from their card.
GET /v1/payments/history?limit=20&cursor=eyJpZCI6IjEyMyJ9
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Query ParameterTypeDescription
limitintegerResults per page. Default 20, max 100.
cursorstringPagination cursor from previous response.
fromstring (ISO 8601)Filter: only transactions after this date.
tostring (ISO 8601)Filter: only transactions before this date.
HTTP/1.1 200 OK
Content-Type: application/json
{
"transactions": [
{
"id": "01JN2X8O2R7J1UDVY0AB1BFMJK",
"call_session_id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"type": "capture",
"window_number": 1,
"amount": "30.00",
"currency": "usd",
"status": "succeeded",
"stripe_payment_intent_id": "pi_1OqBuv2eZvKYlo2CXXXXXXXXXX",
"created_at": "2026-03-15T14:10:00Z"
},
{
"id": "01JN2X9P3S8K2VEWZ1BC2CGNLM",
"call_session_id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"type": "capture",
"window_number": 2,
"amount": "12.00",
"currency": "usd",
"status": "succeeded",
"stripe_payment_intent_id": "pi_2PrCvw3fAuZMmp3DXXXXXXXXXXX",
"created_at": "2026-03-15T14:23:30Z"
}
],
"summary": {
"total_amount": "42.00",
"currency": "usd",
"transaction_count": 2
},
"pagination": {
"cursor": null,
"has_more": false,
"limit": 20
}
}

Hosts must connect a Stripe account before receiving payouts.

POST /v1/stripe/connect/onboard
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

Response:

{
"onboarding_url": "https://connect.stripe.com/setup/e/acct_XXXX/..."
}

Redirect the host to onboarding_url. Stripe handles all identity verification and bank account linking. After completion, Stripe redirects back to Vidivo.

GET /v1/stripe/connect/status
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
{
"stripe_account_id": "acct_1XXXXXXXXXXXXXXXXX",
"charges_enabled": true,
"payouts_enabled": true,
"details_submitted": true,
"requirements": []
}

When charges_enabled and payouts_enabled are both true, the host can receive calls.


Vidivo listens to Stripe webhooks to maintain billing state. All webhooks are verified using Stripe’s webhook signing secret.

EventDescription
payment_intent.succeededHold or capture succeeded
payment_intent.payment_failedPayment failed — triggers call termination
account.updatedHost’s Stripe account status changed
payout.paidHost payout sent to bank
payout.failedHost payout failed — triggers notification
POST https://api.vidivo.app/v1/stripe/webhook

All requests are verified using Stripe-Signature header validation. Do not route Stripe webhooks through Cloudflare proxy — use the direct IP or a trusted proxy.

{
"id": "evt_1OqBuv2eZvKYlo2CXXXXXXXXXX",
"object": "event",
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_1OqBuv2eZvKYlo2CXXXXXXXXXX",
"amount": 3000,
"currency": "usd",
"status": "succeeded",
"metadata": {
"call_session_id": "01JN2X6M0P5H9SRBTXY8ZDKEFG",
"window_number": "1",
"type": "capture"
}
}
}
}

Each billing window creates one Stripe PaymentIntent. The state machine for a single PaymentIntent:

requires_payment_method
requires_confirmation ──► cancel (call never started)
requires_capture ──► cancel (window released)
succeeded (window captured)
StateMeaning
requires_payment_methodInitial hold attempt pending
requires_captureHold authorized — funds reserved
succeededCaptured — funds permanently charged
canceledHold released — no charge

When a call ends mid-window, a partial amount is captured:

partial_amount = ceil(elapsed_minutes_in_window) × rate_per_minute
remaining_amount = window_hold_amount - partial_amount

The PaymentIntent is captured for partial_amount. Stripe releases remaining_amount automatically.


Terminal window
# Get payment history
curl "https://api.vidivo.app/v1/payments/history?limit=20" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."
# Start Stripe Connect onboarding
curl -X POST https://api.vidivo.app/v1/stripe/connect/onboard \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."
# Check Stripe account status
curl https://api.vidivo.app/v1/stripe/connect/status \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."