Skip to main content

Payment events

This guide shows how to read payment events: every schedule payment and agent payment to or from an address, as Remesso records it from Celo, with no node of your own.

What is a payment event?​

Remesso's indexer reads the executor and allowance contracts every minute and records each log as a payment event. That covers every way money moves through Remesso:

  • a schedule's payment, whether Remesso's executor sent it on time or a trigger brought it forward;
  • an agent's payment under a policy;
  • a schedule or policy being created, and a policy being revoked.

Payment events are public and free, like the chain they come from. You read them by address: as the recipient, the payer, or the agent that paid.

List an address's events​

events.list returns one page of events and the next value to pass back for the following page.

import { formatAmount } from "remesso-sdk";

const { events, next } = await receiver.events.list({
role: "to",
kinds: ["run", "policy_payment"],
});

for (const e of events) {
console.log(e.kind, formatAmount(e.amount!, e.token!), e.final ? "final" : "not final yet", e.txHash);
}
console.log("next page after", next);

The query takes these options:

OptionDefaultMeaning
addressthe client's accountThe address to read. Required on a read-only client.
role"any"Which side of the event the address is on: "to", "from", "agent" or "any".
kindsevery kindAny of run, schedule_created, policy_payment, policy_created, policy_revoked.
after0The next of a previous page. 0 starts from the beginning.
limit100Events per page, from 1 to 500.

A read-only client can read any address:

const { events } = await reader.events.list({
address: "0xTheirWallet",
role: "from",
kinds: ["schedule_created"],
});
console.log(events.map((e) => e.refId)); // the ids of the schedules this wallet created

Page through everything​

Events come in the order of their seq, a number that only grows. Pass each page's next back as after until a page comes back short:

import type { PaymentEvent } from "remesso-sdk";

const all: PaymentEvent[] = [];
let after = 0;
for (;;) {
const page = await receiver.events.list({ role: "to", after, limit: 500 });
all.push(...page.events);
after = page.next;
if (page.events.length < 500) break;
}
console.log(`${all.length} events; resume from ${after} next time`);

Keep the last next you processed. Starting from it later returns only what is new or changed since.

Watch for new events​

events.watch is an async iterator. It yields every event from after on, then waits and yields each new one or change as it comes. When it has caught up it polls every intervalMs, 15 seconds by default. Stop it with an AbortSignal.

import { formatAmount } from "remesso-sdk";

const stop = new AbortController();
process.on("SIGINT", () => stop.abort());

let cursor = 0; // load the last seq you handled from your own storage
for await (const e of receiver.events.watch({ role: "to", kinds: ["run", "policy_payment"], after: cursor, signal: stop.signal })) {
console.log(e.kind, formatAmount(e.amount!, e.token!), e.final ? "final" : "not final yet");
cursor = e.seq; // save it, so a restart resumes here
}

Without after, a watch starts from the address's first event. To act only on what arrives from now on, store the seq of the last event you handled and pass it back when you start again.

Wait for one payment​

events.waitFor watches until an event matches, then returns it. It throws a RemessoError with code timeout after timeoutMs, 20 minutes by default.

const paid = await receiver.events.waitFor(
(e) => e.kind === "run" && e.refId === scheduleId.toString(),
{ role: "to", timeoutMs: 15 * 60_000 },
);
console.log("paid in", paid.txHash, "final:", paid.final);

The five kinds​

KindContractrefIdfromtoamount
runexecutorschedule idthe payerthe recipientwhat arrived, after the fee
schedule_createdexecutorschedule idthe payerthe recipientwhat each payment will deliver, after the fee
policy_paymentallowancepolicy idthe policy's ownerthe recipientwhat was paid; agent payments carry no fee
policy_createdallowancepolicy idthe ownernonenone
policy_revokedallowancepolicy idthe ownernonenone

agent is set on the three policy kinds: the agent the policy names.

The fields of an event​

Amounts and ids are strings, because an 18-decimal amount doesn't fit in a JavaScript number. Addresses come back in lowercase, so compare them without regard to case.

FieldTypeMeaning
seqnumberPosition in the feed. It moves whenever the event changes.
chainIdnumber42220, Celo mainnet.
contractaddressThe contract that emitted the log.
kindstringOne of the five kinds above.
refIdstringThe schedule id or the policy id.
from, to, agentaddress or nullThe payer, the recipient and the paying agent, as the table above sets them.
tokenaddress or nullThe token that arrived.
amountstring or nullBase units after the fee: what the recipient received.
amountInstring or nullFor a run, base units before the fee: what left the payer.
memo, memoTextstring or nullA policy payment's 32-byte memo, and the same as text when it is plain text. A request's reference travels here.
runNumbernumber or nullWhich payment of the schedule this run was, from 1.
earlyboolean or nullFor a run, whether a trigger brought it forward.
detailsobjectFacts specific to the kind (below).
txHash, logIndexstring, numberThe transaction and the log inside it. Together they identify the event.
blockNumber, blockHash, blockTimenumber, string, stringWhere and when it was mined.
finalbooleanAt or below Celo's finalized block.
removedbooleanUndone by a reorg.

What details holds depends on the kind:

  • run: nextRunAt, the Unix time the next payment is due. An early payment adds triggeredBy and triggersLeft.
  • schedule_created: interval (seconds), maxRuns, expiresAt, minRateE6, payoutType, feeBps, fundingToken, and builderCodes: the integrator codes read from the transaction that created it (see builder codes). Events recorded before 25 October 2026 have no builderCodes.
  • policy_created: perPayment, perDay, expiresAt and allowlistOnly.

Final and removed​

A payment appears as soon as the indexer sees it, usually within a minute or two of its transaction, with final: false. Celo's finalized block passes it usually 25 to 30 minutes later, and the same event comes round again with final: true and a new seq.

A block above the finalized one can still be replaced by a reorg. If that undoes an event you were shown, it comes round once more with removed: true. Removed events are never deleted, so you can always see what changed.

So decide what each step of your own process needs:

  • show "payment received" as soon as an event arrives with final: false;
  • release anything you can't take back, such as goods or a withdrawal, only once the same event reads final: true;
  • treat removed: true as "that payment didn't happen", and key your records on txHash and logIndex so the later versions of an event replace the earlier one.
tip

For anything valuable, also read the transaction yourself. The event names it in txHash, and the chain is the authority.

Poll, or use webhooks?​

Both read the same payment events.

  • Polling with events.list or events.watch needs no public endpoint. It suits scripts, agents and anything behind a firewall. You keep the cursor.
  • Webhooks push each payment to your server as it happens, signed, and retry for 24 hours if your server is down. They suit a web service that should react at once.

Many integrations use both: webhooks for speed, and a periodic events.list from the last seq to catch anything missed.

The same feed is available over plain HTTP, without the SDK: see payment-events in the HTTP API.