Theme

Start for free
DocsPayments API

03 · Revenue

Record payments from any provider

Selling through something Tally doesn't connect to? Your server posts each payment, and it's linked to its visit like any provider's.

01Make a key that can record payments

On the site's Setup page, under API keys, name a key and tick Record payments. The key is shown once; the list marks it Records payments. A key without it can only read, and is refused here with 403.

02Post each payment

When your server takes a payment, from whatever provider, post it to POST https://tallybyoutpost.com/api/v1/payments with the key as Authorization: Bearer … and a JSON body. Send the buyer's visitor id with it, read from the _tally_vid cookie on their request:

Show the code for

// Express with cookie-parser, where your server records a payment it took.
app.post('/paid', async (req, res) => {
  await fetch('https://tallybyoutpost.com/api/v1/payments', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + process.env.TALLY_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 29.99,
      currency: 'USD',
      transaction_id: 'order_1042',
      tally_visitor_id: req.cookies['_tally_vid'],
      email: 'buyer@example.com',
    }),
  });
  res.sendStatus(204);
});

curl https://tallybyoutpost.com/api/v1/payments \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 29.99, "currency": "USD", "transaction_id": "order_1042", "tally_visitor_id": "the _tally_vid cookie"}'

# Where your server records a payment it took (requests, Flask).
import os
import requests

requests.post(
    "https://tallybyoutpost.com/api/v1/payments",
    headers={"Authorization": "Bearer " + os.environ["TALLY_API_KEY"]},
    json={
        "amount": 29.99,
        "currency": "USD",
        "transaction_id": "order_1042",
        "tally_visitor_id": request.cookies.get("_tally_vid"),
        "email": "buyer@example.com",
    },
    timeout=10,
)

03The fields

amountnumberrequired
What was paid, in the currency's main unit (29.99), tax included. A decimal string works too. More than 0: an amount of 0 is a free trial, which records nothing.
currencystringrequired
Its three-letter ISO 4217 code: USD, EUR, JPY.
transaction_idstringrequired
The payment's id in your system, at most 200 characters (a number works too). It's unique within the site: posting it again is a duplicate, not a second payment.
tally_visitor_idstring
The _tally_vid cookie's value, which ties the payment to its visit. datafast_visitor_id and visitor_id are read the same way.
emailstring
The buyer's email, for an email match. Hashed when it arrives; only the hash is kept. customer_email works too.
namestring
Accepted, so DataFast's body works as it is, and never stored. customer_name too.
customer_idstring
Your id for the buyer. A renewal without a subscription_id follows the customer's first payment.
subscription_idstring
Groups a subscription's payments, so each renewal follows its first payment.
renewalboolean
This payment renews a subscription (subscription_id, else customer_id, says which). It's credited as the first payment was.
refundedboolean
This is a refund of amount against the payment transaction_id names. Partial refunds are fine.
refund_idstring
Your id for the refund, when one payment has several partial refunds. Without it, a second refund of the same payment is a duplicate.
is_free_trialboolean
A trial with nothing paid: nothing is recorded, and the answer is 200.
taxnumberdefault 0
The part of amount that is tax, so revenue is counted without it.
timestampstringdefault now
When it was paid: ISO 8601 with its zone, like 2026-11-10T12:00:00Z. At most a few minutes in the future.

Anything else in the body is ignored.

04What it answers

200 with JSON, whenever the payment is recorded or was already:

200
{"message": "Payment recorded. It's linked to its visit within a few seconds.", "transaction_id": "order_1042", "duplicate": false}
Whenmessageduplicate
A paymentPayment recorded. It's linked to its visit within a few seconds.false
A refundRefund recorded.false
The same transaction_id againThis transaction_id was recorded before; nothing new was recorded.true
The same refund againThis refund was recorded before; nothing new was recorded.true
A free trialFree trials aren't revenue in Tally, so nothing was recorded.false

Anything else is an error, as everywhere in the API: {"error": {"code", "message"}}, where the message is a sentence you can show as it is.

StatuscodeWhen
400invalid_parameterThe body isn't a payment: the message says which field and why.
401unauthorizedNo key, not a key, or a revoked one.
402payment_requiredThe site's account has no plan: its trial ended, or its plan did.
403forbiddenThe key can only read: make one with Record payments ticked.
404not_foundA refund names a transaction_id this site never recorded.
429rate_limitedThe key's rate limit, with Retry-After in seconds.

05Renewals and refunds

A renewal names its subscription (or its customer) and sets renewal; it's credited as the subscription's first payment was:

Renewal
curl https://tallybyoutpost.com/api/v1/payments \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 29.99, "currency": "USD", "transaction_id": "inv_2207", "subscription_id": "sub_88", "renewal": true}'

A refund sets refunded, names the payment it reverses by its transaction_id, and gives the amount refunded. Give each partial refund of one payment its own refund_id:

Refund
curl https://tallybyoutpost.com/api/v1/payments \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10, "currency": "USD", "transaction_id": "order_1042", "refunded": true, "refund_id": "rf_1"}'

06How each payment is linked

By the same rules as every provider, after the answer: a visitor id links with high confidence (as checkout metadata), an email with medium (when the buyer's browser called identify with it), and renewals and refunds follow the payment they belong to. How linking works.

07Check it worked

Something missing, or not what you see? Write to support@tallybyoutpost.com and say which page.