Payment Docs
Basics

Order status and webhooks

Receive payment results via signed webhooks or poll the order by ID

There are two ways to learn the result of a payment:

  1. Webhooks (recommended): we send a signed POST request to your server on every status change.
  2. Polling: you request the order by ID. Use it as a fallback, not as the primary channel.

Webhooks

Where notifications are sent

The webhook address is resolved in this order:

  1. callbackUrl passed when creating the order: applies to that order only;
  2. the webhook URL from your merchant settings (ask your manager to set it).

Your endpoint must accept POST requests with a JSON body and respond with HTTP 200.

Notification format

POST {callbackUrl}
Content-Type: application/json
X-Signature: HMAC-SHA512(request body, API token) in hex

The body is the order object:

{
  "id": "a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6",
  "status": "COMPLETED",
  "purpose": "Payment for order #1234",
  "amount": "1000.5",
  "commission": "1.5",
  "received": "999.0",
  "currency": "RUB",
  "paymentType": "SBP",
  "shopId": "a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6",
  "terminalId": "a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6",
  "merchantId": "a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6",
  "externalId": "order_1234",
  "externalUserId": "user_987",
  "paymentLink": "https://example.com/payment?id=a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6",
  "successUrl": "https://example.com/success",
  "payedAt": "2023-03-21T12:34:56Z",
  "updatedAt": "2023-03-21T12:34:56Z",
  "createdAt": "2023-03-21T12:34:56Z"
}

Verify the signature

Every notification carries the X-Signature header: an HMAC-SHA512 hash of the raw request body in hex, computed with your API token as the secret. Reject requests whose signature does not match.

import crypto from "crypto";

// Your API token is the HMAC secret
const apiToken = process.env.API_TOKEN;

app.post("/webhooks/payments", (req, res) => {
  const signature = req.headers["x-signature"];
  const body = JSON.stringify(req.body);

  // Calculate HMAC-SHA512 of the raw request body and compare in hex
  const expected = crypto.createHmac("sha512", apiToken).update(body).digest("hex");

  if (expected !== signature) {
    return res.status(403).send("Invalid signature");
  }

  // Signature is valid — process the notification idempotently
  const data = req.body;
  if (data.status === "COMPLETED") {
    console.log(`Order ${data.externalId} has been paid`);
  }

  res.status(200).send("OK");
});

Webhook recommendations

  • Always respond with HTTP 200 to confirm receipt. If your server does not respond, we retry the notification.
  • Verify the X-Signature header before processing the body.
  • Process notifications idempotently: the same notification may be delivered more than once.
  • Match the notification to your records by externalId, not by amount.
  • Handle every possible status value, including the unsuccessful ones.

If the same endpoint also receives payout notifications, distinguish them by the X-Type: PAYOUT_UPDATE header, see Payout webhooks.

Poll the order by ID

GET /v1/orders/{orderId}

orderId is the id from the order creation response.

curl -X GET "https://api.riopay.online/v1/orders/a1b2c3d4-e5f6-7g8h-i9j0-k1l2m3n4o5p6" \
  -H "X-Api-Token: YOUR_API_TOKEN"

The response is the order object.

Poll sparingly, for example once every few minutes for orders that have not received a webhook. Frequent polling does not speed up the payment.

Order statuses

StatusFinalDescription
CREATEDNoOrder created, payment link not yet opened
PENDINGNoAwaiting payment
COMPLETEDYesPayment successful, funds received
FAILEDConditionallyPayment error
CANCELEDConditionallyOrder canceled by the system
EXPIREDConditionallyOrder expired before it was paid
BLOCKEDConditionallyTransaction blocked by the bank
REFUNDYesPayment refunded to the payer. Set after COMPLETED
CHARGEBACKYesPayment disputed by the payer and reversed by the bank. Set after COMPLETED

FAILED, CANCELED, EXPIRED and BLOCKED are not strictly final. If the bank confirms the payment later because of an error on its side, the order moves to COMPLETED and the webhook for this change is sent automatically. Be ready to receive COMPLETED for an order you have already marked as unsuccessful.

Only COMPLETED confirms that the funds arrived. A completed order can later move to REFUND or CHARGEBACK when the money is returned to the payer: treat both as a reversal of the payment. FAILED, CANCELED, EXPIRED and BLOCKED are unsuccessful outcomes, but not strictly final: if the bank confirms the payment later because of an error on its side, the order moves to COMPLETED and you receive a webhook automatically. Do not reuse an unsuccessful order for a retry: create a new one.

Some fields may be null depending on the processing stage. Up-to-date request and response schemas are always available in the Swagger documentation (link in the top navigation).

Next steps

On this page