Refunds

Refund a billed period, in full or in part, yourself, within the limits in section 2. A card subscription is paid only by card, so its refund goes back to the card, partial refunds included. Your revenue is reversed for the refunded portion, you bear the whole refunded amount (Invo’s card fee included), a full refund ends the subscription, and Invo emails the member. The last section covers the other way money comes back: a chargeback the member wins with their card issuer. The clients and raw HTTP helpers the samples use are defined on the overview.

1. POST /api/subscriptions/<subscription_id>/refund

Refunds a billed period. Card rail, plus wallet-covered periods on Steam. Idempotent on client_request_id.

FieldTypeReq.RulesError
client_request_idstringyesMax 255. Not sub_-prefixed. One per refund; reuse it on retries.400 CLIENT_REQUEST_ID_REQUIRED, CLIENT_REQUEST_ID_INVALID, CLIENT_REQUEST_ID_TOO_LONG, CLIENT_REQUEST_ID_RESERVED
period_seqinteger >= 1noDefaults to the most recent billed (paid or partly refunded) period. The trial period of a paid trial is 1.400 INVALID_PERIOD_SEQ, 404 PERIOD_NOT_FOUND
amount_usddecimal string > 0noDefaults to the full remaining refundable amount. A partial refund is allowed.400 INVALID_AMOUNT, 400 AMOUNT_EXCEEDS_REMAINING (body carries remaining_refundable_usd, already_refunded_usd)
reasonstring, max 500recommendedCarried on the receipt, the audit trail and the events. Recommended now; it will become required after a notice period. Without it the refund still runs exactly as before (reason is null on the receipt and the events) and the response carries warnings: [{"code": "REASON_RECOMMENDED", ...}]. Longer than 500 characters is cut to 500, with a REASON_TRUNCATED warning.none (never refused today)

Responses

  • 200, status: "refunded": the refund ran. The receipt is in section 4.
  • 202, status: "pending_approval": the refund would exceed your game’s daily self-serve limit, so nothing moved and it became a refund request for Invo to review (section 2). Branch on status; a 202 is not a refund. Refunds are authenticated with your game’s secret key, so a refund request your server did not make means that key was used by someone else: rotate your keys.
  • An error, in the usual {"message", "error_code", ...} envelope (below).

Refusals

HTTPerror_code or bodyMeaningWhat to do
403REFUNDS_NOT_ENABLEDRefunds are turned off for this game. They are on by default for every game; Invo can turn them off for a specific game.Contact Invo.
404SUBSCRIPTION_NOT_FOUNDNot your subscription.Check the id.
409NO_PAID_PERIODNothing has been collected yet; the normal state of a brand-new subscription.Nothing to refund.
409PERIOD_NOT_REFUNDABLE (+ period_status)That period was never collected.Pick a paid period.
409ALREADY_REFUNDED (+ refunded_amount_usd, amount_usd)Fully refunded already.Nothing to do.
409REFUND_WINDOW_CLOSED (+ period_charged_at, refund_window_days, window_closed_at)The period was charged more than 30 days ago.It can no longer be refunded here. A retry of a refund that already ran still returns its receipt.
409REFUND_REQUEST_PENDING (+ request_id)A refund request for this period is waiting for Invo’s review.Wait for subscription.refund_approved or subscription.refund_rejected.
409REFUND_REQUEST_REJECTED (+ request_id, decision_reason)This client_request_id was a refund request Invo rejected.To try again, use a new client_request_id.
409CONCURRENT_REQUESTTwo refund requests for one period were filed at the same moment.Retry with the same key.
409STEAM_REFUND_NOT_SUPPORTEDThe period was charged through Steam. Refused before anything moves.Not supported yet; see section 7.
409MINT_ORDER_MISSING, NO_PAYMENT_REFERENCEThe card charge’s record cannot be found.Contact Invo.
500TENANT_ROWS_MISSINGThe period’s settlement rows cannot be found for your title. Nothing moved.Contact Invo.
502REFUND_LOOKUP_FAILEDNeeds manual attention.Contact Invo.
502REFUND_MISMATCHNeeds manual reconciliation. Also returned when a partial card refund finds a processor refund for this request with a different amount, or the charge already fully refunded outside this flow.Contact Invo; do not retry in a loop.
502PROCESSOR_ERRORThe card processor refused or failed.Retry with the same key; contact Invo if it persists.
503{"error": "service_unavailable"}Temporary.Retry shortly, same key.
503FLOW_PAUSEDRefunds paused for maintenance.Retry later, same key.
503error_code_public: PROCESSOR_NOT_CONFIGUREDThe card processor is not configured for this environment.Contact Invo.
500INTERNAL_ERRORUnexpected.Retry with the same key.
400INVALID_BODYBody is not a JSON object.Send an object.
429RATE_LIMITEDMore than 20 refund calls per minute on one subscription.Slow down.
curl
curl -sS -X POST "$BASE/api/subscriptions/SUB_1757155200_A1B2C3D4/refund" \
  -H "X-Game-Secret-Key: $GAME_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "client_request_id": "refund-2026-11-guild-42-member-7",
    "period_seq": 2,
    "reason": "member request"
  }'
# add "amount_usd": "3.00" for a partial refund; omit period_seq for the most recent billed period
# 200 {"status": "refunded", ...} or 202 {"status": "pending_approval", "request_id": ...}
Node, @invonetwork/web-sdk
import { isRefundPendingApproval } from "@invonetwork/web-sdk/server";

const r = await invo.subscriptions.refund("SUB_1757155200_A1B2C3D4", {
  clientRequestId: "refund-2026-11-guild-42-member-7",   // one per refund; reuse on retry
  periodSeq: 2,                                           // omit for the most recent billed period
  // amountUsd: "3.00",                                   // omit for the full remaining amount
  reason: "member request",                               // recommended; will become required
});
if (isRefundPendingApproval(r)) {
  // HTTP 202, over the daily limit: nothing moved; Invo reviews request r.requestId
} else {
  // r.isFullRefund, r.cardRefundedUsd, r.subscriptionCanceled,
  // r.partnerRevenueReversedUsd, r.processingFeeUsd, r.partnerTotalReversedUsd,
  // r.revenueShareAttribution?.netAttributedAmountUsd
}
// r.warnings?.[0]?.code === "REASON_RECOMMENDED" when no reason was sent
Node, raw HTTP
const { status, json } = await invo("POST", "/api/subscriptions/SUB_1757155200_A1B2C3D4/refund", {
  client_request_id: "refund-2026-11-guild-42-member-7",
  period_seq: 2,
  reason: "member request",
});
if (status === 200) {
  // json.status "refunded"; json.idempotent_replay true on a repeat
} else if (status === 202) {
  // json.status "pending_approval"; json.request_id; nothing moved
} else if (json.error_code === "REFUND_WINDOW_CLOSED") {
  // json.period_charged_at, json.window_closed_at
} else if (json.error_code === "REFUND_REQUEST_PENDING") {
  // json.request_id is waiting for Invo
} else if (json.error_code === "AMOUNT_EXCEEDS_REMAINING") {
  // json.remaining_refundable_usd, json.already_refunded_usd
} else if (json.error_code === "STEAM_REFUND_NOT_SUPPORTED") {
  // a Steam-charged period; nothing moved
}
Python, invonetwork
r = invo.subscriptions.refund(
    "SUB_1757155200_A1B2C3D4",
    client_request_id="refund-2026-11-guild-42-member-7",   # one per refund; reuse on retry
    period_seq=2,                                           # omit for the most recent billed period
    # amount_usd="3.00",                                    # omit for the full remaining amount
    reason="member request",                                # recommended; will become required
)
if r.is_pending_approval:
    pass   # HTTP 202, over the daily limit: nothing moved; Invo reviews request r.request_id
else:
    pass   # r.is_full_refund, r.card_refunded_usd, r.subscription_canceled,
           # r.partner_revenue_reversed_usd, r.card_fee_charged_to_partner_usd, r.partner_debited_usd
Python, raw HTTP
status, res = invo("POST", "/api/subscriptions/SUB_1757155200_A1B2C3D4/refund", {
    "client_request_id": "refund-2026-11-guild-42-member-7",
    "period_seq": 2,
    "reason": "member request",
})
if status == 200:
    pass   # res["status"] == "refunded"; res["idempotent_replay"] True on a repeat
elif status == 202:
    pass   # res["status"] == "pending_approval"; res["request_id"]; nothing moved
elif res.get("error_code") == "REFUND_WINDOW_CLOSED":
    pass   # res["period_charged_at"], res["window_closed_at"]
elif res.get("error_code") == "AMOUNT_EXCEEDS_REMAINING":
    pass   # res["remaining_refundable_usd"], res["already_refunded_usd"]
elif res.get("error_code") == "STEAM_REFUND_NOT_SUPPORTED":
    pass   # a Steam-charged period; nothing moved

2. Refund limits

Refunds are self-serve, inside four limits:

  1. Enabled per game. Refunds are enabled for every game by default. Invo can turn them off for a specific game, and then /refund answers 403 REFUNDS_NOT_ENABLED.
  2. 30 days. A period can be refunded for 30 days after it was charged. After that, 409 REFUND_WINDOW_CLOSED. A retry of a refund that already ran still returns its receipt.
  3. A daily limit per game. Self-served refunds per game per UTC day are limited. By default the limit is never lower than the largest single subscription charge (25000.00 USD today), in sandbox and production, so any single period can always be refunded on its own. If Invo raises the per-charge maximum, the limit rises with it. Invo can set a specific limit for your game, and that limit applies as set, even if it is lower. Reaching the limit exactly still runs; a refund that would exceed it does not run and becomes a refund request (below). A single refund larger than the limit always becomes a request.
  4. A reason. Recommended now, required after a notice period (section 1).

Refund requests (the 202)

A refund over the daily limit answers 202. The body carries every key of the 200 receipt, with the values of a refund that has not happened, plus the request, so a decoder built for the receipt keeps working and simply sees that nothing moved:

{
  "status": "pending_approval",
  "idempotent_replay": false,
  "request_id": "SRR20260917150211AB12CD",
  "request_status": "pending",
  "requested_amount_usd": "40.00",
  "requested_at": "2026-09-17T15:02:11+00:00",
  "daily_cap_usd": "25000.00",
  "executed_today_usd": "24980.00",
  "message": "This refund would exceed the game's daily self-serve refund limit. ...",
  "refund_key": "subrefund:SUB_...:2:<digest>",
  "client_request_id": "refund-2026-11-guild-42-member-7",
  "subscription_id": "SUB_...", "period_seq": 2, "transaction_id": null,
  "refunded_amount_usd": "0.00", "total_refunded_amount_usd": "0.00",
  "remaining_refundable_usd": "40.00", "is_full_refund": false,
  "funding_shape": "card", "balance_delta_coins": "0.00", "card_refunded_usd": "0.00",
  "processor_refund_reference": null, "processor_refund_adopted": false,
  "invo_fee_retained": true, "partner_revenue_reversed_usd": "0.00",
  "partner_revenue_reversal_mode": "none", "reason": "member request", "refunded_at": null,
  "new_balance": "0.00", "revenue_share_attribution": null,
  "initiation": "self_serve", "refund_request_id": "SRR20260917150211AB12CD",
  "subscription_canceled": false, "subscription_status": "active",
  "processing_fee_usd": "0.00", "processing_fee_basis": "none",
  "processing_fee_borne_by": "partner", "processing_fee_transaction_id": null,
  "partner_total_reversed_usd": "0.00",
  "card_fee_charged_to_partner_usd": "0.00", "partner_debited_usd": "0.00"
}
  • Branch on status. pending_approval means nothing has been refunded yet.
  • The same client_request_id returns the same 202 with idempotent_replay: true. While a request is pending, any other refund of that period answers 409 REFUND_REQUEST_PENDING.
  • You also receive subscription.refund_requested (section 6).
  • Approved: the refund runs exactly as if your call had (the 30 days are measured from when you asked). You receive subscription.refund_approved, subscription.refunded and, for a full refund, subscription.canceled. They are sent together and may arrive in any order. A retry of your key now returns the receipt, with initiation: "approved_request" and refund_request_id set. You still bear the whole refunded amount, Invo’s card fee included. Approved refunds do not count toward your daily limit.
  • Rejected: nothing moves. You receive subscription.refund_rejected with decision_reason, and your key answers 409 REFUND_REQUEST_REJECTED. Use a new key to try again; it is a new attempt, subject to the limit.

Your developer console session can list your requests: GET /api/dev/subscriptions/refund-requests?status=pending|approved|rejected|all&game_id=&limit=&offset= (default pending, limit 1 to 100, default 50) returns items (request_id, game_id, subscription_id, item_id, period_seq, client_request_id, amount_usd, reason, status, created_at, decided_at, decision_reason, transaction_id) and total. It reads only games you own (404 GAME_NOT_FOUND otherwise); 400 INVALID_STATUS and 400 INVALID_LIMIT for bad parameters. This is a console read, authenticated by your console session, not by the game key.

3. What is reversed

Card subscriptions are card only (since 2026-09-17): every period is charged to the card in full and the wallet never moves. So a card-only period is refunded in cash, full or partial:

Refund of a card-only periodCardCoins
Full (is_full_refund: true)The rest of the period’s card charge goes back to the card (card_refunded_usd).Nothing (balance_delta_coins: "0.00").
PartialThe refunded amount goes back to the card (card_refunded_usd equals refunded_amount_usd).Nothing.

Partials and the refund that completes them add up to exactly the card charge. Periods charged before 2026-09-17 (funded from the wallet alone, from the card alone, or both), and Steam periods the wallet covered, are refunded as they always were:

Refund of an earlier periodCardCoins
Full (is_full_refund: true)The whole card charge for that period (card_refunded_usd) goes back to the card.The wallet is re-credited with the coins that came from the member’s pre-existing balance; coins that the card charge minted are taken back (the member holds the cash instead). balance_delta_coins is signed and can be negative, and can take the wallet below zero if the member has since spent the coins.
PartialNothing goes back to the card.The refunded amount is credited to the wallet in coins at the game’s rate.
  • Your revenue is reversed for the refunded portion (partner_revenue_reversed_usd, partner_revenue_reversal_mode full or prorata). Invo’s fee is retained (invo_fee_retained: true).
  • You bear the whole refunded amount, Invo’s card fee included (since 2026-09-28), on any refund that returns money to a card, exactly as on a Platform Commerce card refund. Your revenue is reversed, and the rest of the refunded price (Invo’s card fee on that part, net of tax) is deducted from your revenue as its own negative line: card_fee_charged_to_partner_usd. Across partial refunds, and a partial refund followed by a lost chargeback, the fee is charged at most once in total, so the partials of one charge add up to exactly Invo’s fee on it. partner_debited_usd (and partner_total_reversed_usd, the same value) is the revenue reversed plus that fee, which equals the refunded price. A coin refund has no fee.
  • No separate payment processor fee. processing_fee_usd stays on the receipt and reads "0.00", with processing_fee_basis: "none" and processing_fee_transaction_id: null. Before 2026-09-28 the payment processor’s fee on the refund was charged to you here, instead of Invo’s card fee.
  • The international card fee is not returned. The 1.5% charged on a card issued outside the United States stays charged when the period is refunded.
  • Example: a 100.00 USD period on the Open tier, refunded in full. Your share was 96.35 (after the 3.65 card fee); the refund debits 100.00, so you end the period at -3.65. On a card issued outside the United States your share was 94.85 (after the 1.50 international card fee), so you end at -5.15. On Merchant of Record, same non-US card: -6.65.
  • The attribution is corrected: revenue_share_attribution on the receipt and the event carries net_attributed_amount_usd, the corrected figure to pay your recipient for that renewal. Nothing moves.
  • A fully refunded period no longer counts toward paid_through.
  • A full refund cancels the subscription immediately: status canceled, no further charges (subscription_canceled: true, subscription_status: "canceled" on the receipt). You receive subscription.canceled exactly as from the cancel endpoint (canceled_by: "partner"), with the additive cancel_cause: "full_refund". It and subscription.refunded are sent together and may arrive in any order. A partial refund leaves the subscription live. A subscription that was already canceled or expired is not canceled again. (Before 2026-09-17 a full refund did not cancel.)
  • Invo emails the member when a refund executes: the amount, where it went (card or balance), and whether the subscription ended. Your reason is not included. No email is sent for a replay, a refund request or a refusal.
  • A paid trial’s trial period refunds like any other period. It is period 1, charged at the trial price, so period_seq: 1 refunds up to trial_amount_usd, in cash, within the same 30 days and daily limit. You bear the whole refunded amount on it, Invo’s card fee included, as on any card refund; on a small trial price that fee is a large share of the refund. A full refund of the trial period ends the subscription, as a full refund of any period does.

4. Receipt (200)

A full refund of a card-only $9.99 period:

{
  "status": "refunded",
  "idempotent_replay": false,
  "refund_key": "subrefund:SUB_...:2:<digest>",
  "client_request_id": "refund-2026-11-guild-42-member-7",
  "subscription_id": "SUB_...",
  "period_seq": 2,
  "transaction_id": "TXN_...",
  "refunded_amount_usd": "9.99",
  "total_refunded_amount_usd": "9.99",
  "remaining_refundable_usd": "0.00",
  "is_full_refund": true,
  "funding_shape": "card",
  "balance_delta_coins": "0.00",
  "card_refunded_usd": "9.99",
  "processor_refund_reference": "<opaque reference>",
  "processor_refund_adopted": false,
  "invo_fee_retained": true,
  "partner_revenue_reversed_usd": "9.49",
  "partner_revenue_reversal_mode": "full",
  "reason": "member request",
  "refunded_at": "2026-10-20T09:12:44+00:00",
  "new_balance": "20.00",
  "revenue_share_attribution": {
    "recipient_player_id": 4242, "percent": "70.00",
    "original_attributed_amount_usd": "6.64", "refunded_attributed_amount_usd": "6.64",
    "net_attributed_amount_usd": "0.00", "settled_by_invo": false
  },
  "initiation": "self_serve",
  "refund_request_id": null,
  "subscription_canceled": true,
  "subscription_status": "canceled",
  "processing_fee_usd": "0.00",
  "processing_fee_basis": "none",
  "processing_fee_borne_by": "partner",
  "processing_fee_transaction_id": null,
  "partner_total_reversed_usd": "9.99",
  "tax_refunded_usd": "0.00",
  "card_refunded_total_usd": "9.99",
  "card_fee_charged_to_partner_usd": "0.50",
  "partner_debited_usd": "9.99"
}

Open tier, card issued in the United States: Invo’s card fee on 9.99 is 0.50 (3.5% + 0.15) and your revenue was 9.49. The refund reverses the 9.49 and charges you the 0.50, so partner_debited_usd is the whole 9.99.

  • funding_shape is wallet, card or mixed. Every card period charged since 2026-09-17 is card; wallet and mixed appear on earlier periods and on wallet-covered Steam periods.
  • The keys from initiation to partner_total_reversed_usd were added on 2026-09-17. A stored receipt from before that date, returned by a replay, does not carry them.
  • card_fee_charged_to_partner_usd and partner_debited_usd were added on 2026-09-28. A stored receipt from before that release, returned by a replay, does not carry them; read partner_total_reversed_usd there.
  • A replay returns the stored receipt, which never includes new_balance or revenue_share_attribution: those two are on the live response only.
  • warnings (a list of {"code", "message"}) appears only when there is something to say (REASON_RECOMMENDED, REASON_TRUNCATED), and is not part of the stored receipt a replay returns.
  • initiation is self_serve for your own call and approved_request (with refund_request_id set) when Invo approved a refund request.
  • processor_refund_adopted: true means the card refund had already been issued by an earlier attempt that failed before recording it, and this call recorded it; the cash date on your side may differ from refunded_at.
  • A repeated client_request_id returns the stored receipt with idempotent_replay: true (or, if the receipt itself was lost, a reduced body with transaction_id and note: "This refund was already processed.").
  • revenue_share_attribution is null when the subscription has no share.
  • What did I earn: split.partner_revenue_usd on subscription.renewed, minus partner_debited_usd (or partner_total_reversed_usd on a receipt that does not carry it).

5. subscription.refunded

{
  "subscription_id": "SUB_...", "item_id": "guild-42-membership", "period_seq": 2,
  "transaction_id": "TXN_...", "client_request_id": "refund-2026-11-guild-42-member-7",
  "player_email": "member@example.com", "identity_id": "idn_...",
  "amount_usd": "9.99", "total_refunded_amount_usd": "9.99", "period_amount_usd": "9.99",
  "is_full_refund": true, "reason": "member request", "period_status": "refunded",
  "refund": {
    "funding_shape": "card", "balance_delta_coins": "0.00", "card_refunded_usd": "9.99",
    "processor_refund_adopted": false, "new_balance": "20.00", "invo_fee_retained": true,
    "partner_revenue_reversed_usd": "9.49", "partner_revenue_reversal_mode": "full",
    "processing_fee_usd": "0.00", "processing_fee_basis": "none",
    "processing_fee_borne_by": "partner", "partner_total_reversed_usd": "9.99",
    "card_fee_charged_to_partner_usd": "0.50", "partner_debited_usd": "9.99"
  },
  "revenue_share_attribution": { ...as on the receipt... },
  "metadata": {"guild_id": "42"},
  "initiation": "self_serve", "refund_request_id": null, "subscription_canceled": true
}

amount_usd on the event is the amount refunded by this call; total_refunded_amount_usd is cumulative for the period; period_amount_usd is what the period was billed. refund.processing_fee_*, refund.partner_total_reversed_usd, initiation, refund_request_id and subscription_canceled are additive since 2026-09-17; refund.card_fee_charged_to_partner_usd and refund.partner_debited_usd since 2026-09-28. When subscription_canceled is true, asubscription.canceled with cancel_cause: "full_refund" is sent in the same step; the two are sent together and may arrive in any order. Delivery, signature and dedupe are on the webhooks page.

6. Refund request events

subscription.refund_requested fires when a refund over the daily limit becomes a request:

{
  "request_id": "SRR20260917150211AB12CD", "status": "pending",
  "subscription_id": "SUB_...", "item_id": "guild-42-membership", "period_seq": 2,
  "client_request_id": "refund-2026-11-guild-42-member-7",
  "amount_usd": "40.00", "reason": "member request",
  "requested_at": "2026-09-17T15:02:11+00:00",
  "daily_cap_usd": "25000.00", "executed_today_usd": "24980.00",
  "player_email": "member@example.com", "identity_id": "idn_...",
  "metadata": {"guild_id": "42"}
}

subscription.refund_approved is the same with status: "approved", decided_at and transaction_id (the refund). It is sent in the same step assubscription.refunded (and subscription.canceled on a full refund); they are sent together and may arrive in any order. subscription.refund_rejected is the same with status: "rejected", decided_at and decision_reason. These three events do not carry the common subscription block. reason is null when you sent none. Subscribe to them like any other event.

Prove it in sandbox, end to end

  1. Run the card recipe through at least one advance-clock + force-renewal pair, so period 2 is a settled period (funding.card_charged_usd equal to the price on its subscription.renewed).
  2. For a partial first: POST .../refund with a fresh client_request_id, amount_usd: "3.00" and a reason. Expect 200, is_full_refund: false, card_refunded_usd: "3.00", balance_delta_coins: "0.00", card_fee_charged_to_partner_usd above zero, partner_debited_usd: "3.00", processing_fee_usd: "0.00", partner_revenue_reversal_mode: "prorata", subscription_canceled: false. The subscription stays live.
  3. Send the same refund without reason under a new key: it still runs, and the response carries warnings with REASON_RECOMMENDED.
  4. Now refund the rest (no amount_usd, a new key). Expect is_full_refund: true, card_refunded_usd equal to the remainder (the card refunds add up to the charge), subscription_canceled: true, and two deliveries, subscription.refunded and subscription.canceled with cancel_cause: "full_refund", in either order. GET the subscription: status is canceled and paid_through has moved back to the previous paid boundary.
  5. Replay the last body: 200 with idempotent_replay: true, nothing moved. Send another refund for the same period: 409 ALREADY_REFUNDED.
  6. To see a 202, ask Invo to set a low refund limit for your sandbox game (for example 5.00), then refund more than that in one UTC day on that game. At the default limit a single period always runs, so a 202 needs a lower per-game limit. Expect 202 with status: "pending_approval", a request_id, nothing refunded, and a subscription.refund_requested delivery. Ask Invo to approve or reject it in sandbox to see the other two events.

7. Steam

Refunds of periods that were charged through Steam are not supported yet. /refund on such a period answers 409 STEAM_REFUND_NOT_SUPPORTED before anything moves: no coins move, no revenue is reversed, nothing is returned through Steam. A policy for Steam-charged periods is being settled and will be announced.

A period of a Steam subscription that the wallet covered entirely (funding.steam_charged_usd was 0.00 on its subscription.renewed) is refundable in coins: funding_shape: "wallet", the coins go back to the wallet, your revenue is reversed, no processor fee applies, and subscription.refunded fires. The limits in section 2 apply, and a full refund cancels the subscription (Invo also ends the Steam agreement).

8. Chargebacks on a card renewal

A chargeback is the member disputing a card charge with their card issuer instead of asking you for a refund. It is not something you start and there is no endpoint for it. Each card period (the first charge, every renewal, and the trial charge of a paid trial) is its own card charge, so a chargeback always concerns one period.

Chargeback guarantee. Fraud screening is included in the subscription fee on both tiers; the chargeback guarantee is included on Merchant of Record, and on Open-tier charges made before October 21, 2026 (from that date a chargeback on an Open card charge is yours whatever the reason). On both tiers the card network’s dispute fee on a card dispute opened from October 21, 2026 is passed through to you at cost, except when the guarantee covers the chargeback. When the member’s bank raises the chargeback as fraud (the cardholder says they did not authorize the payment), Invo covers it: your share of the period is not reversed, nothing is held while the dispute is open, and no dispute fee is charged to you. purchase.disputed carries guarantee_covered: true, and developer_revenue_reversed_usd reads 0.00. The guarantee covers INVO card charges only; purchases through a third-party storefront or payment rail are unaffected. A chargeback that is not coded as fraud (for example product not received) is yours on both tiers and works as the table below describes. See Tiers.

StageWhat happens
OpenedYour share of the period is held from your earnings while the dispute is open, so it cannot be paid out (it is counted in dispute_holds_usd on GET /api/withdrawals/available-balance), unless the chargeback guarantee covers the dispute. You receive purchase.disputed for the period’s card charge (its order_id is the mint_order_id on that period’s subscription.renewed).
WonThe hold is released. purchase.disputed again, with dispute_status: "won".
LostThe card network has taken the money back, so Invo reverses that period as if it had been refunded in full: your share of the period is reversed (netted from your payout if it was already paid; the hold is released at the same moment, so it is taken only once), and the period stops counting toward paid_through. No other period is touched, and the member’s coin wallet is not debited for the coins the renewal spent (any small rounding remainder still in the wallet is taken back). You receive purchase.disputed with dispute_status: "lost", order_type: "subscription_renewal", subscription_id, period_seq, developer_revenue_reversed_usd, guarantee_covered, card_fee_charged_to_partner_usd and partner_debited_usd. A lost chargeback costs you exactly what a refund of the same period does, on every charge: your share is reversed and Invo’s card fee on the lost amount is charged to you (card_fee_charged_to_partner_usd); partner_debited_usd is the two together. processing_fee_usd is still sent and reads 0.00. On both tiers the card network’s dispute fee on a dispute opened from October 21, 2026 is also passed through to you at cost, with no Invo administrative fee, reported as dispute_fee_usd (a string, or null) and dispute_fee_charged_to_partner (true, false or null); it is credited back if the dispute is won and the network returns it, and a chargeback the guarantee covers carries no dispute fee (see Tiers). When the chargeback guarantee covers it, your share is not reversed and nothing is charged to you.

A lost chargeback does not cancel the subscription. Unlike a full refund, which you ask for, nobody decided to end the membership, so it stays live and the next renewal is charged on schedule. No subscription.refunded and no subscription.canceled are sent. If you do not want to keep billing a member who charged back, cancel the subscription yourself with POST .../cancel and at_period_end: false.

  • Read the chargeback from purchase.disputed: match subscription_id and period_seq, and subtract partner_debited_usd (your share reversed plus Invo’s card fee) from what you recorded for that period on subscription.renewed. It is 0.00 when guarantee_covered is true, and null where it does not apply (for example on a settlement Invo replays); key your books on dispute_id so a figure is recorded once.
  • A period that was already refunded in full, or a period charged before 2026-09-17 that was paid partly from the member’s wallet, is not reversed automatically; Invo reviews it by hand.
  • Keep your consent record complete: it is the evidence a disputed recurring charge is argued from.