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, callback_url, 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.
callback_url 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 full absolute payment URL without the signature parameter (see Signing): same scheme, host, port (if non-default), path, and query string as Rovas will verify, preserving the exact query parameter order you use when building the link, then encoded as application/x-www-form-urlencoded.
price_eur integer optional Price in euro cents. Example: 200 = €2.00. Overrides the project default.
price_chr integer optional Price in chron cents (1 chron = 100 chron cents). 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", "callback_url", "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" },
    "callback_url": { "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(key: project API secret, message: full absolute URL without signature, query order preserved), lowercase hex" },
    "price_eur":   { "type": "integer", "minimum": 1, "description": "Price in euro cents (e.g. 200 = €2.00)" },
    "price_chr":   { "type": "integer", "minimum": 1, "description": "Price in chron cents" },
    "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 callback_url 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 callback_url — 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 (always payment-completed here) so you can use the same endpoint and handler as for Step 3 bank-transfer webhooks — route on event only. Your response code determines whether the buyer is sent to your callback_url or shown an error. Bank transfers do not trigger this webhook at checkout — 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.
token string The original token from the payment request. Use it to look up the pending order in your database.
signature string Webhook signing (different from payment URL signing): HMAC-SHA256 with key = project owner’s API secret and message = UTF-8 bytes of the token string, lowercase hex digest. Always verify before activating access.
amount_paid integer Amount actually paid in minor units of currency (euro cents or chron cents).
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 callback_url. 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", "token", "signature", "amount_paid", "currency", "email", "occurred_at"],
  "properties": {
    "event":       { "type": "string", "const": "payment-completed", "description": "Immediate card/Chron payment settled" },
    "token":       { "type": "string", "description": "Original token from the payment request" },
    "signature":   { "type": "string", "description": "HMAC-SHA256(key: project API secret, message: token), lowercase hex; not the same as payment URL signing" },
    "amount_paid": { "type": "integer", "minimum": 0, "description": "Amount in minor units (euro cents or chron cents)" },
    "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",
  "token": "your-original-token",
  "signature": "your-hmac",
  "amount_paid": 1200,
  "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 shape as Step 2; distinguish flows by event (order-placed, delayed-confirmed, delayed-rejected, or synchronous payment-completed after settlement).

 
 
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=order-placed, token, signature, and related fields. Use this to mark the order as awaiting payment in your system.

 
 
Browser redirect to callback_url

On the Rovas "complete" page the buyer can click "Return to site". The browser is sent to your callback_url 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, pending bank transfer)

Parameter Type Description
token string Original token from the payment request.
signature string HMAC-SHA256(key: project API secret, message: token), lowercase hex. Verify before trusting.
amount_paid integer Order total in euro cents.
currency string Always "EUR" for bank transfers.
email string Buyer's email address.

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(key: project API secret, message: token), lowercase hex.
amount_paid integer Order total in euro cents.
currency string Always "EUR".
email string Buyer's email address.
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.

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", "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(key: project API secret, message: token), lowercase hex" },
    "amount_paid":  { "type": "integer", "minimum": 0, "description": "Order total in euro cents" },
    "currency":     { "type": "string", "const": "EUR" },
    "email":        { "type": "string", "format": "email" },
    "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": 1200,
  "currency": "EUR",
  "email": "buyer@example.com",
  "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": 1200,
  "currency": "EUR",
  "email": "buyer@example.com",
  "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)

Build the message as the full absolute URL Rovas will verify (scheme, host, port if non-default, path /rewpro, ?, query string), with the signature parameter omitted. Query parameters must stay in the same order you use when constructing the link; changing order changes the digest.

Serialize the query as application/x-www-form-urlencoded (same encoding your client library uses when building the redirect URL). Then signature = HMAC-SHA256(key = project API secret, message = that UTF-8 string), lowercase hex.

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
# Payment URL signature (Python-style) — preserve parameter order, omit signature
pairs = [("paytype", "project"), ("recipient", "35384"), ...]  # order matters
query = urllib.parse.urlencode(pairs)
url_without_sig = "https://rovas.app/rewpro?" + query
signature = hmac.new(api_key.encode(), url_without_sig.encode(), hashlib.sha256).hexdigest()

# Webhook JSON signature (different from URL signing)
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
  &callback_url=https%3A%2F%2F40anywhere.xyz%2FpurchaseCallback.html
  &expiration=1750489424
  &email=somebody%40anywhere.xyz
  &lang=en
  &name=Product+name
  &description=Product+description
  &price_eur=800
  &price_chr=8000
  &signature=895991d46af5f989b320f4e04e3959b60723736345cd33483205e0ac307953dc

The sample signature value is illustrative; recompute it with your API key using the payment URL signing rules (exact parameter order as in your final URL). price_eur / price_chr here are €8.00 and 80.00 Chrons expressed in cents.

Rovas Payment Processor · neofund.sk · Developer Info