Rovas payment processor


Integration guide for accepting Chron and Euro payments via Rovas — covering the client-side payment redirect, server-side webhook handling, and the bank-transfer delayed flow.

Overview & setup

The Rovas payment processor lets a web, desktop, or mobile application redirect users to Rovas to pay for a subscription or product in Chrons (CHR) or Euros (EUR). Your application manages its own user database and tracks access to premium features.

HTTPS only: All integration traffic must use https:// — payment links, callbackurl, webhook endpoints, and API hosts. Do not send secrets or payment parameters over plain HTTP.

Environments

Environment Host
Development dev.rovas.app
Production rovas.app

One-time setup steps

1

Create a Rovas account and record your API key from the Rovas API tab of your profile.

2

Create a Rovas project for your application and note its project ID.

3

In the project form, expand Reward sharing and enter your Webhook URL — the server-to-server endpoint Rovas will call after payment.

4

Optional: Set a default minimum price in Chrons or Euros in the project form. These are used as fallbacks when price_chr / price_eur are omitted from the request URL. Euro prices are dynamically converted to Chrons using the current exchange rate.


Step 1 — Client payment request

When a user clicks "Buy" in your app, redirect their browser to the following URL. Rovas renders the order form and handles payment collection.

GEThttps://<server>/rewpro?<parameters>

Query parameters

Parameter Type   Description
paytype string required Entity type to reward. Use "project" in most cases.
recipient integer required ID of the Rovas project (or user) to be rewarded.
token string required Unique opaque string per purchase, echoed in webhooks so you can correlate to an order. Use at least 128 bits of cryptographically secure randomness (e.g. 32 hex chars or a 22-char base64url string). Generate with a CSPRNG: os.urandom / secrets.token_hex(32) in Python, random_bytes(32) in PHP, crypto.randomBytes(32) in Node.js, secrets.randBytes in Ruby, or SecureRandom in Java. Avoid deterministic tokens derived only from predictable order fields. For bank-transfer flows the token is stored server-side with a maximum length of 255 characters; stay within that for interoperability.
expiration integer required UNIX timestamp (seconds) after which this payment link expires.
callbackurl string required URL-encoded URL of your post-purchase landing page. Rovas redirects the buyer here after checkout.
name string required Product name shown on the Rovas order form. No fixed maximum length; keep the full payment URL reasonably short (e.g. under ~2 000 characters total).
description string required Product description shown on the Rovas order form. Same practical length guidance as name.
signature string required HMAC-SHA256 of the canonical URL without the signature parameter (see Signing): same path and base host as Rovas, query parameters sorted by name, then encoded as application/x-www-form-urlencoded. Legacy integrations may still use the exact query-string order from the browser; Rovas accepts either form.
price_eur integer optional Price in whole euros. Example: 2 = €2. Overrides the project default.
price_chr integer optional Price in whole Chrons. Overrides the project default.
email string optional Buyer's email address. Pre-populates the Rovas order form.
lang string optional ISO 639-1 language code (e.g. "en", "sk") for name and description.
JSON schema — payment request parameters
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RovasPaymentRequest",
  "description": "Query parameters for the /rewpro payment redirect URL",
  "type": "object",
  "required": ["paytype", "recipient", "token", "expiration", "callbackurl", "name", "description", "signature"],
  "properties": {
    "paytype":     { "type": "string", "enum": ["project", "user"], "description": "Rewarded entity type" },
    "recipient":   { "type": "integer", "description": "Rovas project or user ID" },
    "token":       { "type": "string", "minLength": 16, "maxLength": 255, "description": "Unique per-purchase token; use 128+ bits CSPRNG (see docs); 255 max when stored for bank transfer" },
    "expiration":  { "type": "integer", "description": "UNIX timestamp — link expiry" },
    "callbackurl": { "type": "string", "format": "uri", "description": "Post-purchase redirect URL (URL-encoded)" },
    "name":        { "type": "string", "description": "Product name shown on order form" },
    "description": { "type": "string", "description": "Product description shown on order form" },
    "signature":   { "type": "string", "description": "HMAC-SHA256(canonical_url_without_signature, api_key) lowercase hex; legacy URL order also accepted" },
    "price_eur":   { "type": "integer", "minimum": 1, "description": "Price in whole euros (e.g. 2 = €2)" },
    "price_chr":   { "type": "integer", "minimum": 1, "description": "Price in whole Chrons" },
    "email":       { "type": "string", "format": "email", "description": "Buyer email — pre-populates order form" },
    "lang":        { "type": "string", "pattern": "^[a-z]{2}$", "description": "ISO 639-1 language code" }
  },
  "additionalProperties": false
}
Link expiry and abandonment (card/Chron): Rovas does not send a server-side webhook if the buyer leaves without completing payment. If the buyer tries to continue checkout after expiration, the browser is redirected to callbackurl with ?error=timeout. Treat your expiration timestamp as the authority for clearing pending "unpaid" tokens on your side. If the user cancels checkout, Rovas may send them back to the Rovas payment entry page rather than to your callbackurl — do not rely on one behaviour for every cancel path.

Step 2 — Webhook (immediate payment)

Upon completion of the payment process (card or Chron), Rovas calls your webhook URL server-to-server via POST with a JSON body (Content-Type: application/json), before redirecting the buyer. The payload includes event and delayed so you can use the same endpoint and handler as for Step 3 bank-transfer webhooks (see event / delayed below). Your response code determines whether the buyer is sent to your callbackurl or shown an error. Bank transfers do not trigger this webhook — see the delayed flow instead.

Rovas uses an HTTP client timeout of 5 seconds for this POST (it does not wait indefinitely). Rovas does not queue automatic retries for this immediate webhook; implement activation logic that is idempotent on token so a duplicate delivery would not double-grant access. The synchronous bank-transfer "payment completed" POST (after a transfer settles) uses the same timeout and is only attempted once per order.

POST{your_webhook_url}
Content-Type: application/json
X-Rovas-Event: payment-completed

Webhook parameters

Parameter Type Description
event string Always "payment-completed" for this webhook. Combine with Step 3 event values (order-placed, delayed-confirmed, …) to route every notification in one handler.
delayed integer Always 0 (payment settled at checkout). Bank-transfer server webhooks use 1 while settlement is still pending.
token string The original token from the payment request. Use it to look up the pending order in your database.
signature string HMAC-SHA256(token, project_api_key) as lowercase hex. Always verify this before activating access.
amount_paid integer Amount actually paid by the buyer (whole number). Unit depends on currency.
currency string "CHR" or "EUR".
email string The buyer's email address.
occurred_at integer UNIX timestamp (seconds) when Rovas sent this webhook. Reject or down-rank events outside a short freshness window (e.g. a few minutes) to limit replay abuse in addition to signature verification.
expiration integer Present when available: the same expiration UNIX timestamp from your payment request (or, for some bank flows, the server-side callback deadline). Compare with occurred_at to ensure the notification is still within the intended validity window without relying on your stored copy alone.
Required response: Return HTTP 204 No Content to confirm successful processing within the 5-second timeout. Rovas then redirects the buyer to your callbackurl. Any other status code suppresses the redirect and shows the buyer: "Thank you for the payment. Due to a temporary problem, the premium features at @project could not be immediately activated. We will remedy the problem, and notify you as soon as possible."
JSON schema — immediate webhook parameters
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RovasWebhookImmediatePayment",
  "description": "JSON body sent by Rovas to the project webhook URL after an immediate (card/Chron) payment",
  "type": "object",
  "required": ["event", "delayed", "token", "signature", "amount_paid", "currency", "email", "occurred_at"],
  "properties": {
    "event":       { "type": "string", "const": "payment-completed", "description": "Immediate card/Chron payment settled" },
    "delayed":     { "type": "integer", "const": 0, "description": "0 = not a pending bank transfer" },
    "token":       { "type": "string", "description": "Original token from the payment request" },
    "signature":   { "type": "string", "description": "HMAC-SHA256(token, project_api_key) as lowercase hex" },
    "amount_paid": { "type": "integer", "minimum": 0, "description": "Amount actually paid (whole number, unit matches currency field)" },
    "currency":    { "type": "string", "enum": ["CHR", "EUR"] },
    "email":       { "type": "string", "format": "email" },
    "occurred_at": { "type": "integer", "description": "UNIX seconds when Rovas emitted this webhook" },
    "expiration":  { "type": "integer", "description": "Optional: payment-link expiration from request (or bank callback deadline)" }
  },
  "additionalProperties": false
}
Example — immediate webhook request
POST /your/webhook/url HTTP/1.1
X-Rovas-Event: payment-completed
Content-Type: application/json

{
  "event": "payment-completed",
  "delayed": 0,
  "token": "your-original-token",
  "signature": "your-hmac",
  "amount_paid": 12,
  "currency": "EUR",
  "email": "buyer@example.com",
  "occurred_at": 1760000000,
  "expiration": 1750489424
}

Step 3 — Bank transfer delayed flow

Bank transfers are not settled at checkout. Rovas uses a three-step delayed flow. Server-to-server POST bodies use the same event + integer delayed convention as Step 2 (delayed is 1 for these bank flows while settlement is incomplete or for bank lifecycle events; Step 2 immediate payments use delayed: 0).

 
 
Webhook: event=order-placed server → server

Sent immediately when the buyer submits the order. Rovas calls your webhook via POST with a JSON body (Content-Type: application/json) including event, token, signature, and delayed=1. Use this to mark the order as awaiting payment in your system.

 
 
Browser redirect to callbackurl with delayed=1

On the Rovas "complete" page the buyer can click "Return to site". The browser is sent to your callbackurl with the parameters below appended as query-string parameters. Show an "awaiting payment" state — access is not yet granted. Verify authenticity via signature before trusting any value.

 
 
Webhook: event=delayed-confirmed or delayed-rejected server → server

Sent after the bank confirms or rejects the transfer. Rovas calls your webhook via POST with a JSON body. If confirmed, activate the user's access. If rejected/expired, notify the user. Rovas retries up to 6 times with backoff on non-2xx responses.

Callback URL parameters (browser redirect, delayed=1)

Parameter Type Description
token string Original token from the payment request.
signature string HMAC-SHA256(token, project_api_key) as lowercase hex. Verify before trusting.
amount_paid integer Order total in whole euros (e.g. 12).
currency string Always "EUR" for bank transfers.
email string Buyer's email address.
delayed integer Always 1. Indicates settlement is pending.

Webhook parameters for settlement events

All server-to-server delayed webhooks (order-placed, delayed-confirmed, delayed-rejected) are POST requests with a JSON body (Content-Type: application/json) and the following fields. Payloads may also include nested order, payment, and context objects for compatibility with older handlers.

Parameter Type Description
event string "order-placed", "delayed-confirmed", or "delayed-rejected".
delivery_id string Idempotency key for this delivery attempt. Use to deduplicate retries.
occurred_at integer UNIX timestamp (seconds) of when the event occurred.
token string Original token from the payment request.
signature string HMAC-SHA256(token, project_api_key) as lowercase hex.
amount_paid integer Order total in whole euros.
currency string Always "EUR".
email string Buyer's email address.
delayed integer Always 1.
expiration integer Optional. When present: original payment-link expiration from the request, or the server-side bank callback deadline — same semantics as the immediate payment-completed webhook.
bank_intent_status string (enum) Bank-payment intent status in Rovas when the webhook was queued (rovas_bank_payment_intent.status). Known values: pending_settlement (usual for order-placed), paid (delayed-confirmed), expired, failed, rejected (delayed-rejected — only these three rejection outcomes are stored). Also possible: manual_review (amount mismatch), created (legacy / DB default), or an empty string if unset. Prefer handling unknown strings defensively for forward compatibility. Same enum as docs/integration/RovasWebhookDelayedEvent.schema.json.

The request also includes HTTP headers:

Header Value
X-Rovas-Event Same as the event query parameter.
X-Rovas-Delivery-Id Same as the delivery_id query parameter.
JSON schema — delayed flow webhook parameters (order-placed / delayed-confirmed / delayed-rejected)
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RovasWebhookDelayedEvent",
  "description": "JSON body for bank-transfer lifecycle webhooks (order-placed, delayed-confirmed, delayed-rejected). Keep bank_intent_status enum in sync with rovas_bank_payment_intent.status in rovas_project_reward.",
  "type": "object",
  "required": ["event", "delivery_id", "occurred_at", "token", "signature", "amount_paid", "currency", "email", "delayed", "bank_intent_status"],
  "properties": {
    "event": {
      "type": "string",
      "enum": ["order-placed", "delayed-confirmed", "delayed-rejected"]
    },
    "delivery_id":  { "type": "string", "description": "Idempotency key — deduplicate retries on this value" },
    "occurred_at":  { "type": "integer", "description": "UNIX timestamp (seconds) of the event" },
    "token":        { "type": "string" },
    "signature":    { "type": "string", "description": "HMAC-SHA256(token, project_api_key) as lowercase hex" },
    "amount_paid":  { "type": "integer", "minimum": 0, "description": "Order total in whole euros (e.g. 12)" },
    "currency":     { "type": "string", "const": "EUR" },
    "email":        { "type": "string", "format": "email" },
    "delayed":      { "type": "integer", "const": 1 },
    "bank_intent_status": {
      "type": "string",
      "enum": ["", "created", "pending_settlement", "paid", "manual_review", "expired", "failed", "rejected"],
      "description": "rovas_bank_payment_intent.status when the delivery was queued. Empty string if status is unset. Rejection webhooks use expired, failed, or rejected only."
    },
    "expiration":   { "type": "integer", "description": "Optional: link expiry or bank callback deadline (UNIX seconds)" }
  },
  "additionalProperties": true
}
Example — delayed-confirmed webhook request
POST /your/webhook/url HTTP/1.1
X-Rovas-Event: delayed-confirmed
X-Rovas-Delivery-Id: cb_0f64d9b0f2f0f1f93dcf2f5cc6d6d3aaf6e2d9a0
Content-Type: application/json

{
  "event": "delayed-confirmed",
  "delivery_id": "cb_0f64d9b0f2f0f1f93dcf2f5cc6d6d3aaf6e2d9a0",
  "occurred_at": 1760000000,
  "token": "your-original-token",
  "signature": "your-hmac",
  "amount_paid": 12,
  "currency": "EUR",
  "email": "buyer@example.com",
  "delayed": 1,
  "bank_intent_status": "paid",
  "expiration": 1750489424
}
Example — delayed-rejected webhook request
POST /your/webhook/url HTTP/1.1
X-Rovas-Event: delayed-rejected
X-Rovas-Delivery-Id: cb_a1b2c3d4e5f6789012345678901234567890abcd
Content-Type: application/json

{
  "event": "delayed-rejected",
  "delivery_id": "cb_a1b2c3d4e5f6789012345678901234567890abcd",
  "occurred_at": 1760000000,
  "token": "your-original-token",
  "signature": "your-hmac",
  "amount_paid": 12,
  "currency": "EUR",
  "email": "buyer@example.com",
  "delayed": 1,
  "bank_intent_status": "expired",
  "expiration": 1750489424
}
Retry behaviour: Each POST uses an HTTP client timeout of 8 seconds. If your webhook returns a non-2xx response or times out, Rovas retries with exponential backoff up to 6 attempts (configurable server-side). Always deduplicate using delivery_id to avoid granting access twice.

Signing & verification

All signatures use HMAC-SHA256 with your Rovas project API key as the shared secret, encoded as a lowercase hex string (digest only, not key-derivation). Use a constant-time comparison when checking webhook signatures.

Outgoing payment link (browser redirect)

Canonical URL (recommended): Build the message exactly as follows so parameter order in the browser does not matter:

  1. Start with the Rovas base URL for the environment: https://rovas.app or https://dev.rovas.app (no trailing slash), plus path /rewpro.
  2. Parse all query parameters except signature.
  3. Sort parameter names in ascending byte order (ASCII / UTF-8 codepoint order for names).
  4. Serialize as application/x-www-form-urlencoded: name1=value1&name2=value2&... using the same encoding your stack uses for encodeURIComponent-style query strings (PHP: http_build_query after ksort).
  5. The string to sign is https://<host>/rewpro? + that serialized query string.
  6. signature = HMAC-SHA256(message = that string, key = API key), lowercase hex.

Legacy: Rovas still accepts a signature computed over the URL with query parameters in the same order as in the incoming request (after removing signature). New integrations should use the canonical form only.

The host and path in the signed string must match what Rovas uses when verifying (production rovas.app, development dev.rovas.app, path /rewpro).

Incoming webhooks

Direction Input to HMAC Purpose
Webhook POST body The token string (UTF-8 bytes) Proves the webhook was sent by Rovas

Optionally enforce freshness: require occurred_at (and, when present, expiration) so that occurred_at ≤ expiration and occurred_at is within a few minutes of your server clock.

Pseudo-code
# Canonical request signature (Python-style)
params = { "paytype": "project", "recipient": "35384", ... }  # no "signature" key
query = urllib.parse.urlencode(sorted(params.items()))  # sorted by key
url_without_sig = "https://rovas.app/rewpro?" + query
signature = hmac.new(api_key.encode(), url_without_sig.encode(), hashlib.sha256).hexdigest()

# Verifying the webhook signature
expected = hmac_sha256(key=api_key, message=webhook_json["token"]).hex()
assert expected == webhook_json["signature"]
# Optional replay window
assert abs(server_unix_time - webhook_json["occurred_at"]) < 300
Security note: Always verify the webhook signature before activating premium access or fulfilling the order. Treat any webhook with a mismatched signature as an unauthorized request and return HTTP 401. Combine with occurred_at (and expiration when provided) for replay protection.

Full request example

https://rovas.app/rewpro
  ?paytype=project
  &recipient=35384
  &token=69e895fb340de7dcd0d6a3e33e56a139a3066224dbd490cee952d3a0cc3f142a
  &callbackurl=https%3A%2F%2F40anywhere.xyz%2FpurchaseCallback.html
  &expiration=1750489424
  &email=somebody%40anywhere.xyz
  &lang=en
  &name=Product+name
  &description=Product+description
  &price_eur=8
  &price_chr=80
  &signature=895991d46af5f989b320f4e04e3959b60723736345cd33483205e0ac307953dc

The sample signature value is illustrative; recompute it with your API key using the canonical signing rules (sorted query parameters). Parameter order in this multiline example is not significant for verification.

Rovas Payment Processor · neofund.sk · Developer Info