Table of contents

Integration quick start

What this covers

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.

Before you start

You needWhere from
Standard API keyDevelopers section of your dashboard
S2S API keyDevelopers section of your dashboard
Brand IDYour dashboard. May require approval before live use
An HTTPS endpointYours, 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/

Step 0 — Check your credentials

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.

Step 1 — Create a client

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.

Step 2 — Create a purchase

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:

FieldUse
idThe purchase id. Every later call uses it
direct_post_urlWhere you submit card data in step 3
checkout_urlPrecium-hosted page, if you would rather not handle card data at all
statuscreated 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.

Two flags to be deliberate about

FlagEffect
skip_capture: falseAuthorise and capture in one step. This is what you want for a simple sale
skip_capture: trueAuthorise 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: trueStore the card. The purchase id then becomes the token for future merchant-initiated charges

Step 3 — Submit the card data

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 rules

FieldFormat
cardholder_nameLatin letters, plus space, apostrophe, dot and dash. Max 30 characters
card_numberDigits only, no spaces. Max 19
expiresMM/YY exactly — two digits, slash, two digits
cvc3 or 4 digits
remember_cardon 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.

Step 4 — Complete 3D Secure

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.

Step 5 — Confirm the outcome

Read the purchase back:

curl -X GET https://gate.reviopay.com/api/v1/purchases/$PURCHASE_ID/ \
  -H "Authorization: Bearer $PRECIUM_API_KEY"
StatusMeaning
paidComplete. Fulfil the order
holdAuthorised only. Capture or release it
preauthorizedCard verified, no money moved
pending_chargeIn flight. Wait for the webhook
errorFailed. 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.

Step 6 — Receive the result by webhook

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:

  1. Verify the signature. Every delivery carries an 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.
  2. Tolerate duplicates. The same event can arrive twice even after you return 200, if the confirmation is lost in transit.
  3. Return 2xx quickly and process asynchronously. Failures are retried up to 8 further times, and nothing is attempted more than 36 hours after the event.

Test cards

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.

When something fails

SymptomMost likely cause
401 UnauthorizedWrong key, or the standard key used where the S2S key is required
400 on purchase creationMissing brand_id, or currency and products placed at the top level instead of inside purchase
direct_post_url is nullsuccess_redirect and failure_redirect not both supplied
409 ConflictThe purchase has already been paid. A purchase may be paid only once
Looks like a decline, but the card is fineA 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.

Next

  • Integration guide — every flow in detail, including preauthorisation, delayed capture, external MPI, network tokens and merchant-initiated charges
  • Test scenarios — the full test matrix to work through before going live
  • Code examples — the same flow in Python, Node.js, PHP, Ruby, Java, C# and Go