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
| Actor | Does |
|---|---|
| Your server | Creates the session, learns the outcome. |
| The payer | Enters card data on the hosted page. |
| Cresora | Hosts 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" }| Field | Meaning |
|---|---|
id | UUID of the session. You poll on this. |
state | PENDING — nobody has paid yet. |
hpp_url | Where the payer goes. Use it as returned. |
expires_at | After 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.
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.
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" }
}| Field | Meaning |
|---|---|
envelope_version | 1 for this envelope shape. |
event_id | UUID of this event. |
event_type | The event name. Not event. |
occurred_at | When the event happened. |
partner_id | Your Partner id. |
idempotency_key | evt_ followed by the event_id. |
data | Every 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
| Network | Card number | CVV |
|---|---|---|
| Visa | 4012000098765439 | 999 |
| MasterCard | 5146315000000055 | 998 |
| Discover | 6011000993026909 | 996 |
| Amex | 371449635392376 | 9997 |
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.
These values are entered on the hosted page. They are never sent to the API.