Quickstart: Hosted payment page
Create a hosted page, open a session for one payment, and send the payer to it without card data touching your server.
WinkPG serves the hosted payment page and the payer enters their card on it. Your server never sees a card number, which keeps the card fields out of your PCI scope entirely.
Two objects matter, and getting the difference right before you write anything saves rework. A page is the reusable configuration: how it looks, what it accepts, where it sends the payer afterward. A session is one payer's one visit, carrying the amount and the order reference. Create the page once. Open a session per payment.
You need the base address of your WinkPG deployment and an API key.
1. Create the page
Do this once, then keep the id. Creating a page per payment is the common first mistake and leaves you with a list you can't manage.
curl -X POST "https://your-gateway-host/api/hostedpaymentpages" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"merchantId": "{merchantId}",
"name": "Online checkout",
"isActive": true,
"allowCustomAmount": false,
"successRedirectUrl": "https://example.test/thank-you"
}'
The response carries the page's id. Save it in your configuration next to your API key.
2. Open a session
curl -X POST "https://your-gateway-host/api/hostedpaymentpages/sessions" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"hostedPageId": "{hostedPageId}",
"label": "Order 1042",
"amountMode": "Locked",
"prefilledFields": { "base_amount": "10.00" },
"expirySeconds": 900
}'
If you take commercial cards, prefilledFields also carries po_number, the customer code /
purchase order the buying organization reconciles the charge against. It's letters and digits only,
up to 25 characters (a reference you write as PO-4471 travels as PO4471). The page decides
whether the field is Hidden, Optional or Required, and the key is accepted only on a page that
renders the field. A malformed value is rejected when you create the session, not when the payer
pays. A prefilled value stays editable so the payer can correct it. See the hosted page's field
configuration for where to turn it on.
amountMode decides who sets the figure. Locked fixes it, which is what an order total wants: the field renders read-only and the server checks every submitted payment against the session's amount, so editing the page or replaying the request reaches the same outcome. Suggested pre-fills a figure the payer can still change, and CustomerEntered (the default) leaves the amount to them.
The same two calls in C#:
using var http = new HttpClient { BaseAddress = new Uri("https://your-gateway-host") };
http.DefaultRequestHeaders.Add("api-key", "YOUR_API_KEY");
var pageResponse = await http.PostAsJsonAsync("/api/hostedpaymentpages", new
{
merchantId = MERCHANT_ID,
name = "Online checkout",
isActive = true,
allowCustomAmount = false,
successRedirectUrl = "https://example.test/thank-you"
});
pageResponse.EnsureSuccessStatusCode();
var hostedPageId = (await pageResponse.Content.ReadFromJsonAsync<JsonElement>())
.GetProperty("id").GetString();
var sessionResponse = await http.PostAsJsonAsync("/api/hostedpaymentpages/sessions", new
{
hostedPageId,
label = "Order 1042",
amountMode = "Locked",
prefilledFields = new Dictionary<string, string> { ["base_amount"] = "10.00" },
expirySeconds = 900
});
sessionResponse.EnsureSuccessStatusCode();
var sessionId = (await sessionResponse.Content.ReadFromJsonAsync<JsonElement>())
.GetProperty("id").GetString();
3. Get the payment link
Ask for the address rather than assembling one from the session id. The link is issued for this session, and an assembled one stops working the moment the address scheme changes.
curl "https://your-gateway-host/api/hostedpaymentpages/sessions/{sessionId}/payment-link" \
-H "api-key: YOUR_API_KEY"
The response body is the address itself, as a plain string:
https://pay.your-environment.example/s/8f14e45fceea167a
4. Send the payer to it
Redirect the browser at that address:
HTTP/1.1 303 See Other
Location: https://pay.your-environment.example/s/8f14e45fceea167a
The payer enters their card on the page WinkPG serves, and is returned to the successRedirectUrl you configured.
Treat that return as a hint that the payment finished, not as proof of it. A payer who pays and then closes the tab never comes back, and nothing about your success page can tell you whether that happened. The webhook is what tells you, and it arrives either way.
5. Reconcile from the webhook
Subscribe to the transaction events, verify the signature on each delivery, and take the transaction id from the body. Then read the transaction the same way any other integration does:
curl "https://your-gateway-host/api/transactions/{transactionId}" \
-H "api-key: YOUR_API_KEY"
However the payment was collected, the record it produces is one transaction.
Next steps
- Quickstart: Webhooks sets up the receiver this page depends on for reconciliation.
- Setting up a hosted payment page covers page modes, branding, and the options you skipped above.
- Hosted payment page iframe integration covers embedding the page in your own checkout instead of redirecting away from it.