The shortest path to a successful test transaction: create a client, create a purchase, submit card data, complete 3D Secure, confirm the result. Six requests.
Once this works, the Integration guide covers every flow in depth — preauthorisation, delayed capture, external MPI, network tokens, merchant-initiated charges and the full webhook event set.
| You need | Where from |
|---|---|
| Standard API key | Developers section of your dashboard |
| S2S API key | Developers section of your dashboard |
| Brand ID | Your dashboard. May require approval before live use |
| An HTTPS endpoint | Yours, for the 3D Secure return and for webhooks |
Use your test keys throughout. Test and production share one base URL and are separated only by the key you send, so there is no URL to get wrong — and no URL-level protection if you send the wrong key.
https://gate.reviopay.com/api/v1/ Confirm the key works before building anything:
curl -X GET https://gate.reviopay.com/api/v1/clients/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" An empty result set is success. A 401 means the key is wrong or malformed; check for a stray newline or a missing Bearer prefix.
A client is the customer record that purchases attach to. Only email is required, and it must be unique.
curl -X POST https://gate.reviopay.com/api/v1/clients/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "buyer@example.com",
"full_name": "Test Buyer",
"phone": "+27821234567"
}' Keep the returned id — that is your client_id.
You can skip this step entirely by passing an inline client object when creating the purchase. Precium will find or create the client from the email. Creating the client explicitly is clearer when you already hold customer records.
Three fields are required: client_id (or an inline client), purchase, and brand_id. All amounts are integers in cents — 149900 is R1,499.00.
curl -X POST https://gate.reviopay.com/api/v1/purchases/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "00000000-0000-4000-8000-000000000001",
"brand_id": "00000000-0000-4000-8000-000000000002",
"purchase": {
"currency": "ZAR",
"language": "en",
"products": [
{ "name": "Order 1042", "price": 149900 }
]
},
"payment_method_whitelist": ["visa", "mastercard"],
"reference": "ORDER-1042",
"force_recurring": false,
"skip_capture": false,
"success_redirect": "https://www.example.com/success",
"failure_redirect": "https://www.example.com/failure",
"cancel_redirect": "https://www.example.com/cancel"
}' From the response, keep:
| Field | Use |
|---|---|
id | The purchase id. Every later call uses it |
direct_post_url | Where you submit card data in step 3 |
checkout_url | Precium-hosted page, if you would rather not handle card data at all |
status | created at this point |
If direct_post_url is null, it is because success_redirect and failure_redirect were not both supplied. Both are required for the direct post flow.
| Flag | Effect |
|---|---|
skip_capture: false | Authorise and capture in one step. This is what you want for a simple sale |
skip_capture: true | Authorise only. Funds are held and you must later /capture/ or /release/. With a purchase total of 0 this becomes preauthorisation — card verification with no financial transaction |
force_recurring: true | Store the card. The purchase id then becomes the token for future merchant-initiated charges |
Post the card fields to the direct_post_url from step 2, with ?s2s=true. This request uses your S2S API key, not the standard one.
curl -X POST "$DIRECT_POST_URL?s2s=true" \
-H "Authorization: Bearer $PRECIUM_S2S_KEY" \
-H "Content-Type: application/json" \
-d '{
"cardholder_name": "Test Buyer",
"card_number": "4111111111111111",
"expires": "12/29",
"cvc": "123",
"remember_card": "off",
"remote_ip": "196.0.0.1",
"user_agent": "Mozilla/5.0",
"accept_header": "text/html",
"language": "en-US",
"java_enabled": false,
"javascript_enabled": true,
"color_depth": 24,
"utc_offset": 0,
"screen_width": 1920,
"screen_height": 1080
}' | Field | Format |
|---|---|
cardholder_name | Latin letters, plus space, apostrophe, dot and dash. Max 30 characters |
card_number | Digits only, no spaces. Max 19 |
expires | MM/YY exactly — two digits, slash, two digits |
cvc | 3 or 4 digits |
remember_card | on to store the card, off otherwise |
Validate these yourself before submitting. A format error is treated as a payment failure, not as a validation response, so a malformed expiry date looks like a decline.
The browser fields from remote_ip down are the 3D Secure device fingerprint. Send them for customer-initiated payments; they materially affect whether the issuer challenges the cardholder.
If the card is enrolled, the response carries 3D Secure parameters. Post the customer's browser to the issuer, then return them to the callback.
<form id='tds' method='POST' action='{{URL}}'>
<input type='hidden' name='MD' value='{{md}}'>
<input type='hidden' name='PaReq' value='{{PaReq}}'>
<input type='hidden' name='TermUrl' value='{{callback_url}}'>
<noscript><button type='submit'>Continue to your bank</button></noscript>
</form>
<script>document.getElementById('tds').submit();</script> Although MD, PaReq and PaRes are 3DS1 parameter names, 3D Secure 2 is fully supported — Precium performs the 3DS2 verification and challenge behind this simpler redirect contract. You do not implement 3DS2 yourself.
If the card is not enrolled, authorisation completes synchronously and there is nothing to redirect.
Read the purchase back:
curl -X GET https://gate.reviopay.com/api/v1/purchases/$PURCHASE_ID/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" | Status | Meaning |
|---|---|
paid | Complete. Fulfil the order |
hold | Authorised only. Capture or release it |
preauthorized | Card verified, no money moved |
pending_charge | In flight. Wait for the webhook |
error | Failed. Read transaction_data.attempts[], newest first, for the error code |
Do not treat the browser redirect as confirmation. A customer who closes the tab still completes a payment, and you will never see the redirect. Webhooks are the authoritative source.
Register an endpoint once:
curl -X POST https://gate.reviopay.com/api/v1/webhooks/ \
-H "Authorization: Bearer $PRECIUM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Order events",
"callback": "https://www.example.com/webhooks/precium",
"events": ["purchase.paid", "purchase.payment_failure"]
}' Three things your handler must do:
X-Signature header — a base64-encoded RSA PKCS#1 v1.5 signature of the SHA-256 digest of the raw request body. Get the key from public_key on the webhook object. Verify the raw bytes, before parsing.Card numbers for the sandbox are listed in Test scenarios, alongside the expected outcome for each. Use 123 as the CVC for a successful verification.
The examples above use 4111111111111111, the standard Visa test number. Confirm the current set against Test scenarios before building an automated test suite.
| Symptom | Most likely cause |
|---|---|
401 Unauthorized | Wrong key, or the standard key used where the S2S key is required |
400 on purchase creation | Missing brand_id, or currency and products placed at the top level instead of inside purchase |
direct_post_url is null | success_redirect and failure_redirect not both supplied |
409 Conflict | The purchase has already been paid. A purchase may be paid only once |
| Looks like a decline, but the card is fine | A card field failed format validation — check expires is MM/YY |
Full error codes, retry guidance and issuer response codes are in Error mapping and Issuer response codes.