Transactions API
Report a purchase, refund, or deletion from your own backend, independent of any specific payment processor.
Recording a purchase
Call this from your backend right after a successful checkout — e.g. from a payment webhook. It works the same way regardless of which payment processor you use, since it's a plain HTTP endpoint rather than a processor-specific integration.
curl -X POST https://api.shonylabs.com/v1/transactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transaction_id": "order_9F2K",
"shonylabs_visitor_id": "<value of the shonylabs_visitor_id cookie>",
"amount": 49.00,
"currency": "USD",
"customer_email": "[email protected]",
"renewal": false,
"metadata": {
"plan": "pro",
"sku": "PRO-ANNUAL",
"coupon": "LAUNCH20"
}
}'| Field | Required | Notes |
|---|---|---|
transaction_id | Yes | Your own order ID. Acts as an idempotency key — retried webhook deliveries record the sale once. |
shonylabs_visitor_id | Yes | Read from the first-party shonylabs_visitor_id cookie the tracking snippet sets — this links the sale back to the visitor who made it. |
amount | Yes | A non-negative number, e.g. 49.00. |
currency | No | Up to 10 characters, e.g. USD. |
customer_email / email | No | Either key name is accepted. Helps match free trials to later payments. |
customer_name / name | No | Either key name is accepted. |
customer_id | No | Stable customer ID from your own billing system. |
renewal | No | Boolean. Whether this is a recurring renewal rather than a first purchase. |
is_free_trial | No | Boolean. Whether this represents a free trial rather than a paid charge. |
metadata | No | A flat JSON object of your own key/value pairs — plan name, product SKU, coupon code, anything you want attached to this specific sale. Up to 20 keys; each key up to 64 characters (letters, numbers, underscore, or hyphen) and each value up to 500 characters. Values are coerced to strings and unrecognized keys/values beyond those limits are dropped rather than rejecting the whole request. |
timestamp | No | ISO 8601 event time. Defaults to the time the request is received. |
Metadata shows up in the dashboard's Transactions pageas a small tag icon next to the transaction ID — hover it to see every key/value pair. It can also be edited directly from that page's Edit action, independent of this API.
Refunds
Call the same endpoint again with refunded: true and the original transaction_id — the revenue is netted back out rather than deleting the original sale.
curl -X POST https://api.shonylabs.com/v1/transactions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transaction_id": "order_9F2K",
"refunded": true
}'Deleting a payment
Use DELETE /v1/transactions to fully remove a wrongly-recorded payment — unlike a refund, this deletes the transaction and its revenue attribution outright rather than netting it out. Provide exactly one filter: a single transaction_id (or tx_id), a shonylabs_visitor_id to remove every payment for one visitor, or a start/end ISO 8601 range to bulk-delete by when the payment was recorded.
curl -X DELETE "https://api.shonylabs.com/v1/transactions?transaction_id=order_9F2K" \
-H "Authorization: Bearer YOUR_API_KEY"curl -X DELETE "https://api.shonylabs.com/v1/transactions?start=2026-05-01T00:00:00Z&end=2026-05-19T23:59:59Z" \
-H "Authorization: Bearer YOUR_API_KEY"A successful response returns { "status": "success", "data": [...] }, with one entry per deleted payment. Deleting a transaction_id that doesn't exist returns 404; the visitor and date-range filters return an empty data array instead when nothing matches.
No backend? Report revenue from the browser instead
This API is the recommended way to record revenue because it's authenticated with your site's API key — a visitor can never fake a payment through it. If your checkout has no backend of its own to call it from (a static site or a no-code storefront, for example), turn on Allow revenue from the browser tracker under Settings → API and call this from the page after a successful checkout instead — for example on a Stripe Checkout success_url page, or wherever your storefront redirects after payment:
window.shonylabs('purchase', {
revenue: 19.99,
currency: 'USD',
transaction_id: 'order_9F2K'
});transaction_idis optional but recommended — it isn't a reserved property, so it's stored as an ordinary custom field alongside the event, visible in the visitor's activity feed. This setting is off by default: enabling it means anyone who opens devtools on your site can also call window.shonylabs('purchase', { revenue: 999999 }) and report fake revenue, so only turn it on if you accept that trade-off.
POST /v1/transactions, this path has no server-side idempotency check on transaction_id— calling it twice for the same order records the revenue twice. If the same page can fire this more than once (a visitor refreshing a "thank you" page is the common case), guard it client-side:const orderId = 'order_9F2K';
if (sessionStorage.getItem('shonylabs_reported_order') !== orderId) {
window.shonylabs('purchase', { revenue: 19.99, currency: 'USD', transaction_id: orderId });
sessionStorage.setItem('shonylabs_reported_order', orderId);
}