Payment statuses

Complete state machine: request status, proof status, Trails status, and display logic.

Overview

Every payment in JoPay has three independent status fields that combine to determine what the user sees. Understanding these statuses is essential for integrating with webhooks and building accurate payment UIs.

Request status

The request_status tracks the lifecycle of the payment request itself — whether it has been created, viewed by the customer, or expired.

StatusMeaningTransitions to
requestedPayment request has been created by the merchant. The customer has not yet opened the payment link.opened, expired
openedThe customer has opened the payment link and can see the payment details.expired
expiredThe payment request has passed its expiration time without a verified payment. This is a terminal state.None (terminal)

Request status diagram

requested ──→ opened ──→ expired
    │                        ↑
    └────────────────────────┘

Proof status

The proof_status tracks whether an on-chain payment proof has been attached and verified for this request.

StatusMeaningTransitions to
noneNo payment proof has been submitted. The customer has not yet sent USDC.attached
attachedA proof (transaction hash) has been submitted but not yet verified by the Trails engine.verified
verifiedThe Trails engine has verified the on-chain transfer matches the payment request. This is the terminal success state.None (terminal)

Proof status diagram

none ──→ attached ──→ verified
A payment is only verified when proof_status is verified. The attached status means a transaction hash has been submitted but the on-chain data has not yet been validated. Do not treat attached as payment verification.

Trails status

The trails_status tracks the state of the Trails verification engine as it checks the on-chain proof. This is primarily used internally and in webhook payloads.

StatusMeaningTransitions to
pendingA verification job has been queued but has not started yet.in_progress
in_progressThe Trails engine is actively checking the blockchain for the transaction.success, failed, cancelled
successVerification succeeded. The on-chain transfer matches the payment request.None (terminal)
failedVerification failed. The transaction does not match (wrong amount, recipient, asset, or chain).None (terminal)
cancelledThe verification was cancelled (e.g., the request expired before verification finished).None (terminal)

Trails status diagram

pending ──→ in_progress ──→ success
                  │
                  ├──→ failed
                  │
                  └──→ cancelled

Display status logic

The merchant dashboard and payment pages do not show all three status fields separately. Instead, they combine into a single display status that describes the overall state of the payment. The logic is:

Display statusConditionShown as
Awaiting paymentproof_status = none and request is not expiredNeutral / default state
Verifyingproof_status = attached (Trails is checking)In-progress indicator (spinner or pulse)
Paidproof_status = verifiedGreen success indicator
Expiredrequest_status = expired and proof_status != verifiedGrey / muted indicator
Failedtrails_status = failedRed error indicator
The display status prioritizes proof_status = verified above all else. Even if a request has technically expired, if the proof was verified before or shortly after expiry, the display status will show Paid.

Complete state diagram

┌─────────────────────────────────────────────────┐
│                PAYMENT LIFECYCLE                │
│                                                 │
│  Request:   requested → opened → expired        │
│                                                 │
│  Proof:     none → attached → verified          │
│                                                 │
│  Trails:    pending → in_progress → success     │
│                                   → failed      │
│                                   → cancelled   │
│                                                 │
│  Display:   Awaiting  → Verifying → Paid        │
│             payment                             │
│                  └──→ Expired                   │
│                           └──→ Failed           │
└─────────────────────────────────────────────────┘

Webhook events and statuses

Webhooks fire on proof status transitions:

  • payment.proof_attached — Fires when proof_status transitions from none to attached.
  • payment.proof_verified — Fires when proof_status transitions from attached to verified.

See Webhook events for full payload schemas and signature verification.