Quickstart: Direct API
Authenticate with an API key and take your first card payment over the WinkPG API.
Your server holds the card details and posts them to WinkPG. This is the shortest path if you already handle card data under your own PCI scope, or if you are charging a card the payer isn't present for.
You need two things: the base address of your WinkPG deployment, and an API key. Both come from your integration contact if you don't have them yet.
Every request below sends the key in an api-key header. No login call, and no token to refresh.
1. Prove the key works
Ask for the transaction list. An empty items array is a success: it means the key authenticated and the account simply has nothing in it yet.
curl "https://your-gateway-host/api/transactions" \
-H "api-key: YOUR_API_KEY"
{
"items": [],
"totalCount": 0
}
A key that's not accepted comes back as HTTP 401 with a stable code you can branch on:
{
"error": "Unauthorized",
"code": "KEY_INVALID",
"message": "API key is not valid."
}
None of the 401 codes is transient, so don't retry a refused key. Log the code and never the key itself.
2. Take a payment
One request creates the transaction and runs it. 4111111111111111 is the network test number and is the only card number that belongs in a code sample.
curl -X POST "https://your-gateway-host/api/transactions" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"idempotencyKey": "quickstart-0001",
"cardData": {
"cardNumber": "4111111111111111",
"nameOnCard": "Jane Doe",
"expirationMonth": 12,
"expirationYear": 2030
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'
The same call in C#:
using var http = new HttpClient { BaseAddress = new Uri("https://your-gateway-host") };
http.DefaultRequestHeaders.Add("api-key", "YOUR_API_KEY");
var response = await http.PostAsJsonAsync("/api/transactions", new
{
transactionType = "Sale",
// Reproduce this value on a retry so a timed-out request replays instead of charging twice.
idempotencyKey = "quickstart-0001",
cardData = new
{
cardNumber = "4111111111111111",
nameOnCard = "Jane Doe",
expirationMonth = 12,
expirationYear = 2030
},
invoiceData = new
{
amounts = new { @base = 10.00m, total = 10.00m }
}
});
response.EnsureSuccessStatusCode();
var created = await response.Content.ReadFromJsonAsync<JsonElement>();
var transactionId = created.GetProperty("id").GetString();
The response is the created transaction. The two properties worth reading first are its id, which every later call addresses it by, and the outcome under responseData:
{
"id": "6f3b2c18-0a4d-4a9e-9d4f-2b71c2f0a911",
"transactionType": "Sale",
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
},
"responseData": {
"resultCode": "Ok",
"resultMessage": "APPROVED",
"authorizationCode": "TST123"
}
}
resultCode is the value to branch on. Ok is the gateway accepting the request; anything else names what went wrong, and a refusal by the issuer arrives here rather than as a failed HTTP call.
Card data is optional in a sample but not always at the processor: send cvv alongside the card fields when you have collected it, since many processors require it for a card-not-present sale. It's absent above by design, because a published example should never teach that echoing or storing the value is normal.
3. Read the transaction back
curl "https://your-gateway-host/api/transactions/{transactionId}" \
-H "api-key: YOUR_API_KEY"
Wire this in from the start rather than trusting the create response alone. A request that times out in transit leaves you with no response and a payment that may well have gone through, and the read is how you find out which.
4. Handle the refusal path
A declined payment is a normal outcome, not a transport error: it arrives as a successful HTTP response whose responseData carries the refusal. Branch on the result, never on the HTTP status alone.
Your deployment's sandbox refuses specific amounts on purpose so you can exercise that branch before you go live. The testing page on the developer documentation site lists the amounts your environment reacts to, and it reads them from the simulator rather than restating them, so they can't drift.
Next steps
- Getting started with the API covers idempotency, rate limits, timestamps, and what an API key can reach.
- Webhook integration covers receiving the transaction result at your own endpoint instead of polling for it.
- Reusing a stored payment method with payment tokens covers charging a stored card again without holding the number.