1 · Before you start
You need an agent already connected to COLVO — see connect your own agent or the n8n, Flowise and Voiceflow guides. Testing needs no Paddle account: COLVO simulates one. For production you need a Paddle Billing account (API keys start with pdl_; Paddle Classic is not supported).
On Paddle COLVO can refund, cancel (now or at the end of the billing period), pause, resume and undo a scheduled cancel. Plan changes and discounts work on Stripe only for now.
2 · Run the Paddle scenarios
Open Test runs → Choose scenarios…, set Simulated provider to Paddle, pick scenarios and start. Your agent’s tools don’t change — it still proposes to COLVO. What changes is the account behind it:
- Paddle-shaped ids — customers
ctm_…, subscriptionssub_…, transactionstxn_…. Refunds name the transaction inpayment_id. - No idempotency keys. Paddle has none, so a lost response must not become a second refund — COLVO tags every refund and looks for it before any retry. The scenario retry after a lost response proves it.
- Refunds wait for review. Paddle checks refunds before paying out. In refund waits for Paddle’s review no money has moved yet — your agent must say “requested”, not “refunded”.
3 · Propose a refund (Node and Python)
The same SDK call as on Stripe — only the ids are Paddle’s. Map the result to the reply with messageFor: it knows that a refund Paddle is still reviewing is requested, not done.
import { Colvo, messageFor } from '@colvo/sdk';
// Backend (backend key): the facts come from your systems, never from the model.
const backend = new Colvo({ apiKey: process.env.COLVO_BACKEND_KEY });
const mandate = await backend.mandates.create({
project_id: process.env.COLVO_PROJECT_ID, source_request_id: ticket.id,
subject: { customer_id: 'ctm_01j8…', payment_ids: ['txn_01j9…'], subscription_ids: ['sub_01j8…'] },
actions: ['refund.create', 'subscription.cancel'],
limits: { currency: 'eur', max_amount_minor: 4000, auto_allow_up_to_minor: 4000, max_operations: 1 },
});
// Agent (agent key): propose instead of calling Paddle.
const colvo = new Colvo({ apiKey: process.env.COLVO_AGENT_KEY });
const op = await colvo.operations.propose({
mandate_id: mandate.mandate_id, source_request_id: ticket.id, action: 'refund.create',
parameters: { payment_id: 'txn_01j9…', amount_minor: 2900, currency: 'eur' },
});
// While Paddle reviews the refund: "Your €29.00 refund is requested — …", never "refunded".
reply(messageFor(op));import os
from colvo import Colvo, message_for
colvo = Colvo(os.environ["COLVO_AGENT_KEY"])
op = colvo.operations.propose({
"mandate_id": mandate_id, "source_request_id": ticket_id, "action": "refund.create",
"parameters": {"payment_id": "txn_01j9…", "amount_minor": 2900, "currency": "eur"},
})
reply(message_for(op)) # PROVIDER_REVIEW → "requested", VERIFIED → "confirmed"
# Later (a job, or the next customer message): has Paddle approved it?
op = colvo.operations.wait_for(op["operation_id"], until="verified", timeout=30)4 · Connect Paddle for production
In Paddle → Developer tools → Authentication, create an API key with Transactions read, Subscriptions read and write, Adjustments read and write, Customers read — nothing else. In COLVO open your project → Add a connection → Paddle Billing, paste the key and press Check and connect. COLVO checks each permission without creating anything, and refuses a key that can also change products, prices or discounts.
Then in Paddle → Developer tools → Notifications, add a destination with the URL COLVO shows and the events adjustment.created, adjustment.updated, subscription.updated, subscription.canceled, subscription.paused, subscription.resumed. Paste its secret key into COLVO. Remove the Paddle key from your agent — only COLVO’s executor uses it.
5 · What happens to a real refund
Guard checks the proposal against the mandate (limits count what the customer gets back, tax included — Paddle is the merchant of record). On ALLOW, COLVO creates one Paddle refund and the operation shows Waiting for Paddle. When Paddle approves it, the adjustment.updated notification (or COLVO’s hourly check) turns it VERIFIED. If Paddle rejects it, the operation is FAILED, the budget is released and an incident tells you to let the customer know.
Next: set refund limits and read a FAIL.