Skip to main content
Cresora Commerce
Getting Started

Anatomy of a first payment

What each actor does, and what every session and webhook-envelope field means.

The Quickstart gets a payment through. This page explains what actually happened, one level deeper.

Three actors

ActorDoes
Your serverCreates the session, learns the outcome.
The payerEnters card data on the hosted page.
CresoraHosts the page, charges the card, emits the event.

Your server never sees the card. That is the point of the flow.

The session

POST /hpp/sessions takes three fields — merchant_id (a UUID), amount as a decimal string, and currency — and returns:

{ "id": "0190a1c4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "state": "PENDING",
  "hpp_url": "<absolute URL of the hosted page>",
  "expires_at": "2026-08-12T14:47:07Z" }
FieldMeaning
idUUID of the session. You poll on this.
statePENDING — nobody has paid yet.
hpp_urlWhere the payer goes. Use it as returned.
expires_atAfter this, the page stops accepting payment.

Sessions expire — 900 seconds by default. An expired one cannot be revived: create a new one.

The payment

The payer opens hpp_url and types their card into a page Cresora serves. The card number, expiry, and CVV travel from the payer's browser to Cresora — never through your server, your logs, or your database.

🔒Compliance

Because card data never reaches your systems, this flow is designed to keep your cardholder-data footprint minimal — commonly the SAQ A shape. Which SAQ applies to you is your QSA's call, not ours.

⚠Warning

A raw card number posted to the Cresora API is rejected with 400. There is no supported path for sending one.

The outcome

A captured payment emits transaction.captured:

{
  "envelope_version": 1,
  "event_id": "0190a1e6-7081-7c9d-8e0f-3a4b5c6d7e8f",
  "event_type": "transaction.captured",
  "occurred_at": "2026-08-12T14:32:09Z",
  "partner_id": "0190a190-2b3c-7d4e-8f50-6a7b8c9d0e1f",
  "idempotency_key": "evt_0190a1e6-7081-7c9d-8e0f-3a4b5c6d7e8f",
  "data": { "transaction_id": "0190a1d5-6f70-7b8c-9d0e-2f3a4b5c6d7e",
            "state": "CAPTURED", "amount": "10.00" }
}
FieldMeaning
envelope_version1 for this envelope shape.
event_idUUID of this event.
event_typeThe event name. Not event.
occurred_atWhen the event happened.
partner_idYour Partner id.
idempotency_keyevt_ followed by the event_id.
dataEvery business field lives here.

Deduplicate on idempotency_key: a key you have already processed is a no-op. Redelivery is expected, not exceptional.

Without webhooks, poll GET /hpp/sessions/{id} — its transaction_id fills in when state becomes COMPLETED — then read GET /transactions/{transactionId}.

When it declines

A decline is a business outcome, not a transport error. The read returns 200 with state set to FAILED and a decline_code explaining why — there is no 4xx to catch.

A declined attempt does not close the session. It stays PENDING, so the payer can try another card until expires_at.

Where the transaction goes next

On the card path a successful payment moves CAPTURED → SETTLED. The remaining states — INITIATED, AUTHORIZED, SUBMITTED, VOIDED, REFUNDED, PARTIALLY_REFUNDED, REVERSED, DISPUTED, CHARGED_BACK, VERIFIED, THREE_DS_PENDING, THREE_DS_FAILED — belong to other paths and rails. See Transaction lifecycle.

Two things people look for and do not find here: there is no manual-capture field on POST /transactions, and type: "AUTHORIZATION" returns 501. Auth-only is a hosted-page option, capture_mode: "authorize" — see the Hosted Payment Page guide. Charging a card you already saved is a SALE with vault_token and use_type; see Tokenization and Direct API.

Sandbox values

NetworkCard numberCVV
Visa4012000098765439999
MasterCard5146315000000055998
Discover6011000993026909996
Amex3714496353923769997

All four use expiry 12/28.

The card number selects the network. The outcome comes from the amount's cents portion — .00 approves — and from the CVV: 999 approves, 111 declines.

💡Tip

These values are entered on the hosted page. They are never sent to the API.