Skip to content

Event types

Eleven events across four kinds of subject: payments, batches, deposits and off-ramps. The authoritative list, generated from the API source, is in the API reference.

Payments

Event Fires when Typical reaction
transaction.quoted A price has been locked for the payment Record the rate
transaction.funded Funds have left your wallet and are held by JET Mark the customer's funds as committed
transaction.paid_out The local partner confirms the recipient has been paid Tell your customer the money has landed
transaction.reconciled Books, chain, and provider all agree. The payment is done Settle your own accounting
transaction.refunded The payment was cancelled and the full amount returned to your home wallet Return the funds to your customer
transaction.in_doubt The outcome is ambiguous and has been parked Alert operations. Do not retry the payment

Each item in a batch is an ordinary payment, so it sends these events too, under its own transaction ID (<batchId>:<itemRef>).

Batches

Event Fires when
batch.rejected The batch was refused before anything moved: no item started, nothing was funded
batch.completed No item is still running. Not the same as every item paid

batch.completed carries counts, because "finished" and "paid" are different claims:

{
  "deliveryId": "dlv_91f0…",
  "event": "batch.completed",
  "transactionId": "payroll-2026-10-15",
  "data": {
    "batchId": "payroll-2026-10-15",
    "items": { "RECONCILED": 997, "NEEDS_ATTENTION": 3 }
  }
}

batch.rejected carries data.reason, saying why.

Deposits and off-ramps

Event Fires when
onramp.delivered A deposit converted and the USDC is in your wallet
onramp.refunded A deposit could not be completed and the payer's money was returned
offramp.paid An off-ramp paid the beneficiary

data carries the deposit or off-ramp ID (onRampId / offRampId), its state, the sendAmount, and, once known, receiveAmount and fee. A delivered deposit adds txHash; an off-ramp adds beneficiaryId.

Envelope

Every delivery has the same shape:

{
  "deliveryId": "dlv_6ac1…",
  "event": "transaction.paid_out",
  "transactionId": "pay-2026-08-21-0001",
  "data": { "…": "…" }
}
Field Type Notes
deliveryId string Stable across retries. De-duplicate on this
event string One of the events above
transactionId string The subject's ID: the payment, batch, deposit or off-ramp. For anything you started, the idempotency key you chose
data object Event-specific payload

For a payment event, data carries the state, the payment's own terms, and any amounts known at that point:

{
  "state": "PAID_OUT",
  "sendAmount": { "amount": "100000", "currency": "USD", "scale": 2 },
  "receiveAmount": { "amount": "24512750", "currency": "VND", "scale": 0 },
  "corridor": { "from": "USD", "to": "VND", "destinationCountry": "VN" },
  "occurredAt": "2026-08-21T09:14:02.000Z",
  "provider": "tazapay",
  "payoutMode": "regular"
}
Field Present when
state Always
sendAmount Always: what you asked us to send
corridor Always
occurredAt Always: when this transition happened, UTC
receiveAmount Once the recipient amount is known
provider Once a route has been chosen
payoutMode Once a route has been chosen. Always regular today

New fields may be added to data, and new event types may be added. Ignore ones you do not recognise rather than rejecting the delivery.

Amounts are integer minor units. See Amounts and currencies.

Which events actually matter

For most payment integrations, three:

transaction.paid_out: the recipient has the money. This is the moment your customer wants to hear about.

transaction.reconciled: the payment is genuinely finished and the final amounts are settled. This is the moment your ledger should hear about.

transaction.in_doubt: you need a human.

quoted and funded are useful for a detailed progress UI and noise otherwise.

Do not settle on paid_out

The recipient has been paid, but the books have not yet been proven to agree with the provider and the chain. If the confirmed amount turns out to differ from the quoted one, that difference surfaces during reconciliation.

Show paid_out to your customer; settle your own accounting on reconciled.

in_doubt is not a failure

It means the honest answer is "we don't know yet": a provider call timed out, or a response was ambiguous, and we would rather park the payment than guess.

JET re-queries the provider on a schedule and the payment resumes on its own.

So: alert your operations team, and do not start a replacement payment. A replacement is how an ambiguous payment becomes a confirmed double-payment.

A payment waiting for an operator sends no event

A payment whose funds have moved but which cannot be paid out yet retries on its own (PENDING_RETRY) and then waits for our operations team (NEEDS_ATTENTION). Neither state sends a webhook: the payment is not finished, and nothing has gone back to you. Read the state from GET /transactions/:id. See How a payment works.

Ordering

Events are not guaranteed to arrive in order. Two deliveries for the same payment can cross.

Guard your state machine accordingly: keep the state you have already recorded, and ignore an event that would move a payment backwards. If you need certainty at any moment, read GET /transactions/:id: that is always authoritative.

Notification emails

Team members can be emailed about a subset of these events. Those are for humans; webhooks are for systems. See Notifications.