anything.solutions

Payments

Request a payment from a member, or send one to them with AnythingPay.

Platform fees
Payment typeOur feeHow it is charged
Application payment 0.75% Added to the payer's total at checkout.
Donation 0% We take nothing from donations.
Card top-up or card donation Processor cost Added to the card charge, so the balance or donation arrives in full. The rate follows the fees Stripe actually charged.
Crypto cash-out Network fee Deducted from the payout and shown before you confirm.

AnythingPay works in both directions. An application can send a member to a checkout page to collect a payment, or move USD from the application owner's balance to a member. Both flows settle in one transaction and leave a receipt.

Applications are created in the Applications tool. Payment requests are available to confidential applications only, because the status check uses the client secret.

Requirements

For a checkout:

  • The application and its owner are active, and the application has a client secret.
  • The redirect_uri exactly matches one registered for the application.
  • The member is signed in, has two-factor authentication enabled, and is not restricted from sending payments.
  • The member has enough balance, and the payment is inside the per-payment and daily limits.

A payout needs only an active, signed-in recipient to accept or decline it.

Guest payments

A checkout link can also be paid by card by someone who is not signed in and does not have a anything.solutions account. Guest checkouts are card-only: the visitor pays through Stripe, the application owner receives the full requested amount, and the platform fee and card processing cost are added to the card charge. Balance payments still require signing in with two-factor authentication.

Guest access is only available when card payments are configured. A request that names a specific payer still requires that member to sign in; a guest cannot pay on their behalf.

Create a checkout session

The application's server creates the request, authenticates with its client secret, and receives a checkout link. This proves the request came from the application, so another site cannot build a checkout under its name.

import json
import requests

response = requests.post(
    "https://anything.solutions/pay/sessions",
    auth=("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
    data={
        "redirect_uri": "https://example.com/paid",
        "amount": "4.99",
        "currency": "USD",
        "description": "Premium role",
        "items": json.dumps([{"name": "Premium role", "quantity": 1}]),
        "state": "RANDOM_STATE",
        "reference": "order-1042",
        "payer": "482",
    },
)
session = response.json()

The response contains a short-lived checkout link and the reference:

{
  "ok": true,
  "reference": "order-1042",
  "checkout_url": "https://anything.solutions/pay/checkout?request=...",
  "expires_in": 900
}

Redirect the member's browser to checkout_url. The link expires with the configured checkout lifetime and can be opened by the intended member only when payer is set. The amount is fixed when the session is created.

For a simple payment link you can also build the checkout URL directly. The application name and registered return address still make the request identifiable, but a server-created session is the stronger option. Use an unguessable reference (or omit it and let the server generate one) when the link is shared; a reference is settled only once.

Redirect the member to the checkout endpoint with these query parameters:

ParameterValue
client_idThe application's client ID.
redirect_uriOne of the application's registered redirect URIs.
amountA positive USD amount with up to two decimal places.
currencyUSD (default).
descriptionA short line describing the purchase. Optional.
itemsA URL-encoded JSON array of items. Optional.
stateAn unguessable value returned unchanged on the callback. Optional.
referenceYour own reference, 1 to 64 letters, digits, dots, colons, underscores or dashes. Optional; a random one is generated when omitted.
payerThe member's sub from AnythingAuth. Optional; when set, the checkout refuses to charge a different signed-in account.
https://anything.solutions/pay/checkout?client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fexample.com%2Fpaid&amount=4.99&currency=USD&description=Premium%20role&state=RANDOM_STATE&reference=order-1042

Items are names with an optional quantity. Keep the total amount in amount; item amounts are not used.

[
  {"name": "Premium role", "quantity": 1},
  {"name": "Server slot", "quantity": 2}
]

The checkout page shows the application, the amount in USD, the items, the member's balance, and whether the payment fits the daily allowance. The member enters an authenticator code or a backup code and chooses Pay or Cancel.

Callback

After the member decides, the browser returns to your registered redirect_uri with:

ParameterValue
paymentThe payment reference.
statuspaid or denied.
stateThe value you sent, unchanged, when one was provided.

The browser callback is a hint, not proof. Confirm the result on your server before delivering anything, and compare the reference, status and amount from the status endpoint with the order you created.

Confirm a payment

Call the status endpoint from your server with the client ID and secret over HTTP Basic. Send the reference as a form field:

import requests

response = requests.post(
    "https://anything.solutions/pay/status",
    auth=("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
    data={"reference": "order-1042"},
)
payment = response.json()

A settled payment returns:

{
  "ok": true,
  "payment": {
    "reference": "order-1042",
    "kind": "charge",
    "status": "paid",
    "amount_cents": 499,
    "fee_cents": 4,
    "total_cents": 503,
    "amount_usd": 4.99,
    "requested_amount": 4.99,
    "requested_currency": "USD",
    "description": "Premium role",
    "items": [{"name": "Premium role", "quantity": 1}],
    "created_at": "2026-10-27T18:40:12Z",
    "paid_at": "2026-10-27T18:40:19Z",
    "payer": {"id": 482, "username": "example"},
    "payee": {"id": 91, "username": "app-owner"},
    "app": {"client_id": "YOUR_CLIENT_ID", "name": "Example App"}
  }
}

Unknown references return 404. A reference that belongs to another application is treated as unknown.

Send a payment to a member

An application can pay a member from the balance of the account that owns it. This is the reverse of a checkout: the application authenticates the request with its client secret, and the member accepts or declines the pending payment instead of approving a checkout.

import requests

response = requests.post(
    "https://anything.solutions/pay/send",
    auth=("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
    data={
        "recipient": "482",
        "amount": "10.00",
        "currency": "USD",
        "description": "Balance payout",
        "reference": "payout-1042",
    },
)
payment = response.json()
ParameterValue
recipientThe member's sub from AnythingAuth. Required.
amountA positive USD amount with up to two decimal places.
currencyUSD (default).
descriptionA short line describing the payment. Optional.
itemsA URL-encoded JSON array of items. Optional.
referenceYour own reference for idempotency. A random one is generated when omitted.

The response is the same payment object returned by the status endpoint, with kind set to payout. A newly created payout is pending and includes a claim_url for the recipient. Reusing a reference with the same payout details returns the original payment without creating another.

The recipient receives a notification and opens a payment page showing the application, amount, description, items, sender, reference and date. They can accept or decline there. No money moves until they accept, so poll the status endpoint before delivering anything. A payout ends as paid or denied.

The application owner's balance must cover the amount when the recipient accepts, and the daily outgoing limit applies at that point. The recipient sees the payment in their Payment history.

Monthly subscriptions

A confidential application can charge a member's balance automatically each month. The member first approves the application through AnythingAuth with the Payments and subscriptions scope, then the application creates a subscription with the member's current access token. The token must belong to the same application that authenticates with its client secret; a bare user id is never accepted.

The application calls these endpoints over HTTP Basic with its client ID and secret. Every call also includes the member's user_token:

EndpointPurpose
POST /pay/api/subscriptionsCreate a subscription, or return the identical one for the same service and reference.
POST /pay/api/subscriptions/statusRead the current status, next charge date and failure count.
POST /pay/api/subscriptions/updateChange amount_cents, metadata or next_charge_at.
POST /pay/api/subscriptions/cancelStop future charges. Repeating the call is harmless.
import requests

response = requests.post(
    "https://anything.solutions/pay/api/subscriptions",
    auth=("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
    json={
        "user_token": "MEMBER_ACCESS_TOKEN",
        "service": "pro-monthly",
        "reference": "customer-482",
        "amount_cents": 500,
        "interval_months": 1,
        "next_charge_at": "2026-04-01T00:00:00Z",
        "metadata": {"plan": "pro"},
    },
)
subscription = response.json()["subscription"]
FieldValue
serviceYour product slug, up to 64 characters. Combined with reference, it uniquely identifies the subscription for your app.
referenceYour own reference for this member's subscription, up to 64 characters. Reusing it with the same amount is idempotent.
amount_centsThe recurring price in Points. The platform fee is added to the member's total; your app receives the full amount.
interval_months1. Monthly subscriptions are supported today.
next_charge_atOptional ISO-8601 first charge time. Omit it to charge on the next scheduler pass.
metadataOptional JSON object returned with the subscription and its webhook events.

A subscription is active, past_due or cancelled. A charge debits the member's balance in one transaction and writes the same receipt and Points ledger entries as a checkout. If the balance is too low, nothing is debited: the subscription becomes past_due, and the scheduler retries once a day until the member tops up or the application cancels. A successful charge advances the next date by one month from the period that was charged, so a late run does not move the billing anniversary. If the member revokes the application in Settings, the next scheduler pass cancels its subscriptions before charging.

The scheduler is flask --app wsgi charge-subscriptions. It is safe to run repeatedly and can be wired to cron or a systemd timer. The process-jobs --loop worker also calls it on the interval in ANYTHINGPAY_SUBSCRIPTION_INTERVAL_SECONDS.

Members can create shareable payment links for donations without registering an application. Open the Payment links tab, add a title and optional description, and choose up to six suggested amounts in USD. Each link gets a short public address and a PNG QR code that can be downloaded from its management page.

Donors open the link, choose a suggested amount or enter their own, and approve the checkout with two-factor authentication. Funds move immediately to the link owner and the donation appears in both members' Payment history. Links can be paused or deleted at any time; past donations keep their receipts.

Donors can also pay by card through Stripe. The card charge settles to the link owner's balance and produces the same donation receipt; the donor does not need an account or a balance.

The Keys tab lists the client IDs and secret status of apps that request payments server-to-server. Payment links do not need an app or a key.

For automation, a confidential app can manage its owner's payment links with its client credentials over HTTP Basic. Get the client ID and secret from the Keys tab or the Applications tool. The API returns the public link, QR endpoint, suggested amounts and donation totals.

The response includes url (the page to send donors to) and qr_url (a PNG QR code).

EndpointPurpose
GET /pay/api/donation-linksList the owner's links with totals.
POST /pay/api/donation-linksCreate a link. Fields: title, optional description, optional suggested_amounts.
GET /pay/api/donation-links/<id>Read one link and its totals.
POST /pay/api/donation-links/<id>Update title, description, suggested_amounts or active.
GET /pay/api/donation-links/<id>/donationsList paid donations, newest first. Optional page and limit.
GET /pay/api/donation-links/<id>/qr.pngDownload the link's QR code.

Webhooks

Members can register up to five HTTPS endpoints in the Webhooks tab. When a payment settles, AnythingPay posts one signed JSON event to every active endpoint of the payment recipient. Only the recipient's endpoints receive the event; the payer is never notified by webhook.

EventSent when
donation.paidA donation link was paid, by balance checkout or by Stripe card checkout.
payment.paidAn application checkout payment settled.
payout.paidThe recipient accepted a pending application payout.
subscription.chargedA monthly subscription charged successfully. The event includes the receipt.
subscription.payment_failedA monthly charge could not be collected and the subscription is past_due.
subscription.cancelledThe application cancelled a subscription.

Deliveries use POST with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
User-AgentAnythingPay-Webhooks/1.0
X-AnythingPay-EventThe event type, such as donation.paid.
X-AnythingPay-Signaturet=<unix>,v1=<hex digest>, an HMAC-SHA256 over <timestamp>.<raw body>.

The endpoint must answer with a 2xx status within 5 seconds. Redirects are not followed. Transient failures are retried with backoff (30 seconds, 2 minutes, 10 minutes, then 1 hour) for up to five attempts, and the response status and the first 500 characters of the response body are recorded in the Webhooks tab.

{
  "id": "pay_evt_1042_donation_paid",
  "type": "donation.paid",
  "created": 1761700000,
  "data": {
    "object": {
      "id": 1042,
      "reference": "donation-7a91",
      "amount_usd": 5.0,
      "currency": "USD",
      "status": "paid",
      "description": "Keep the servers running",
      "payer": {"id": 482, "username": "example"},
      "payee": {"id": 91, "username": "app-owner"},
      "donation_link": {"id": 12, "title": "Support my project", "slug": "support-me"},
      "client": null
    }
  }
}

Verify the signature against the exact raw request body before parsing it, and reject a timestamp that is more than a few minutes old:

import hashlib
import hmac
import time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    try:
        parts = dict(item.split("=", 1) for item in header.split(","))
        timestamp = int(parts.get("t", ""))
        signature = parts.get("v1", "")
    except (TypeError, ValueError):
        return False
    if abs(time.time() - timestamp) > 300:
        return False
    signed = f"{timestamp}.".encode("ascii") + raw_body
    expected = hmac.new(
        secret.encode("utf-8"), signed, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Store each event's id and ignore a replay. Delivery is at least once; the signature header is not covered by the timestamp check alone. Trust the signed body's type, not the unsigned X-AnythingPay-Event header.

Rotating an endpoint's secret takes effect immediately. The new secret is shown once, like the first one. Remove the old secret from your receiver at the same time.

Security

  • Two-factor authentication is required to approve a checkout. It cannot be approved with only the account session.
  • The member sees the application, amount, items and description before approving. The checkout data is bound to the request; it cannot be changed by editing the page.
  • Checkout payments settle immediately. Payouts stay pending until the recipient accepts. Do not deliver goods or services until the status endpoint returns paid.
  • Payouts debit the application owner's balance only when the recipient accepts. Keep the client secret server-side; anyone with it can create payments from that balance within the configured limits.
  • Reuse the same reference when retrying a checkout link. A settled reference never moves money twice.
  • The amount is settled in USD. The exact amount is shown before approval.
  • Per-payment and daily limits apply. A request that exceeds them is refused before any balance changes.
  • Members can review their sent and received payments under Payment history.
  • Application names are self-registered and are not verified. The checkout shows the app owner; compare the status response before delivering anything.