UniWeb Merchant API

Collect payments on your website or app. Create payment links, redirect customers to UniWeb checkout, poll status, and receive signed webhooks — all with your UniWeb merchant keys. Bank and payment-rail credentials stay on UniWeb; you never handle partner secrets.

OpenAPI 3.0 (openapi.json) → Login for API Keys →

Quick Start

  1. Sign up and copy your API key (uw_test_… or uw_live_…) and secret (uws_…) from Dashboard → API Settings.
  2. POST JSON to https://uniweb.co.in/api/v1/ with headers X-API-Key, X-API-Secret, and Content-Type: application/json.
  3. For write calls, add a unique Idempotency-Key header so retries never double-charge.
  4. Redirect the customer to the returned payment_url (UniWeb hosted checkout).
  5. Configure your webhook URL and verify X-UniWeb-Signature on every delivery.

Authentication

POST https://uniweb.co.in/api/v1/

Legacy alias https://uniweb.co.in/api.php accepts the same requests. Every JSON response includes api_version: "v1".

HeaderRequiredDescription
X-API-KeyYesMerchant API key — uw_test_… (sandbox) or uw_live_… (live)
X-API-SecretYesPaired secret — uws_…
Idempotency-KeyWrite callsUnique string (max 100 chars) for create_payment_link and create_refund
Content-TypeYesapplication/json

Mode is determined by the key prefix — not a separate header. Test keys only return test data and test checkout; live keys require completed KYC and live activation. Browser calls must allowlist your origin under API Settings.

Create Payment Link

action: create_payment_link · scope: links:write · Idempotency-Key required

Creates a one-time payment link. Redirect the customer to payment_url — UniWeb hosted checkout handles UPI, cards, and netbanking. No partner-branded buttons are shown to your customer.

Request body:

{
  "action": "create_payment_link",
  "amount": 500,
  "description": "Order #123",
  "customer_phone": "9876543210",
  "customer_name": "Rahul Sharma"
}

cURL:

curl -X POST 'https://uniweb.co.in/api/v1/' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: uw_test_your_key_here' \
  -H 'X-API-Secret: uws_your_secret_here' \
  -H 'Idempotency-Key: order-123-attempt-1' \
  -d '{"action":"create_payment_link","amount":500,"description":"Order #123","customer_phone":"9876543210"}'

Success response (HTTP 200):

{
  "success": true,
  "api_version": "v1",
  "mode": "test",
  "link_id": "LNK20260826123456",
  "payment_url": "https://uniweb.co.in/checkout.php?link=LNK20260826123456",
  "amount": 500,
  "expires_at": "2026-08-27 18:30:00"
}

Error example (HTTP 400):

{
  "success": false,
  "error_code": "amount_out_of_range",
  "error": "Amount must be between 1 and 200000000.",
  "api_version": "v1"
}

Fields: amount (required, INR, 1–200000000) · description (optional, max 255) · customer_phone / customer_name (optional). Links expire after 24 hours.

Check Payment Status

action: check_status · scope: transactions:read

Poll a transaction after checkout. Use the txn_id from webhooks or your dashboard. Status is scoped to your API key mode (test vs live).

Request body:

{
  "action": "check_status",
  "txn_id": "TXN20260826123456"
}

cURL:

curl -X POST 'https://uniweb.co.in/api/v1/' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: uw_test_your_key_here' \
  -H 'X-API-Secret: uws_your_secret_here' \
  -d '{"action":"check_status","txn_id":"TXN20260826123456"}'

Success response (HTTP 200):

{
  "success": true,
  "api_version": "v1",
  "transaction": {
    "txn_id": "TXN20260826123456",
    "amount": "500.00",
    "status": "success",
    "payment_method": "upi",
    "utr": "123456789012",
    "created_at": "2026-08-26 18:05:22"
  }
}

Typical status values: pending, success, failed. Prefer webhooks for real-time updates; use polling as a fallback.

Outbound Webhooks

Set your HTTPS webhook URL and signing secret in Dashboard → API Settings. UniWeb POSTs JSON to your server when payments complete. Always verify the HMAC signature — never trust the body alone.

When you rotate your signing secret in API Settings, UniWeb keeps the previous secret valid for 48 hours so deliveries in flight still verify. Update your server to accept both secrets during that window, then drop the old one.

Delivery headers:

Content-Type: application/json
X-UniWeb-Event: payment.success
X-UniWeb-Event-Id: EVT20260826123456
X-UniWeb-Signature: <hmac-sha256-hex-of-raw-body>
User-Agent: UniWeb-Webhook/1.0

Payload body:

{
  "id": "EVT20260826123456",
  "event": "payment.success",
  "created_at": "2026-08-26T12:34:56+00:00",
  "data": {
    "txn_id": "TXN20260826123456",
    "amount": 500,
    "status": "success",
    "payment_method": "upi",
    "utr": "123456789012",
    "link_id": "LNK20260826123456"
  }
}

Events: payment.success · payment.failed · refund.completed · webhook.test (from API Settings → Send Test Webhook).

Verify signature (PHP — copy-paste):

<?php
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_UNIWEB_SIGNATURE'] ?? '';
$signingSecret = 'your_webhook_signing_secret'; // from API Settings

$expected = hash_hmac('sha256', $raw, $signingSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($raw, true);
// Handle $event['event'] and $event['data']
http_response_code(200);
echo 'ok';

Verify signature (Node.js):

const crypto = require('crypto');

function verifyUniWebWebhook(rawBody, signature, signingSecret) {
  const expected = crypto.createHmac('sha256', signingSecret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ''));
}

// Express example: use express.raw({ type: 'application/json' }) for the route
// const ok = verifyUniWebWebhook(req.body, req.get('X-UniWeb-Signature'), process.env.UNIWEB_WEBHOOK_SECRET);

Failed deliveries retry with exponential backoff (up to 8 attempts). Return HTTP 2xx quickly; process asynchronously if needed. X-UniWeb-Event-Id is stable across retries — use it for idempotent handling on your side.

SDK Libraries

Official UniWeb client libraries — same Merchant API as above, UniWeb brand only. No partner SDK wrappers; your customer never sees bank/PG product names.

Quick start: copy uw_test_… + uws_… from Dashboard → API Settings → install an SDK below → call createPaymentLink → redirect to payment_url. Full README: sdk/php · sdk/node.

PHP SDK

Package: uniweb/merchant-sdk

Monorepo: github.com/uniwebadmin/uniweb/sdk/php

Install (path — until Packagist publish):

composer config repositories.uniweb-merchant-sdk path ../uniweb/sdk/php
composer require uniweb/merchant-sdk:*

Create payment link:

use UniWeb\Client\Client;
use UniWeb\Client\ClientConfig;

$uniweb = new Client(new ClientConfig(
    apiKey: 'uw_test_your_key_here',
    apiSecret: 'uws_your_secret_here',
    mode: ClientConfig::MODE_TEST,
));

$link = $uniweb->createPaymentLink([
    'amount' => 500,
    'description' => 'Order #123',
    'customer_phone' => '9876543210',
]);
header('Location: ' . $link['payment_url']);

Node.js SDK

Package: uniweb

Monorepo: github.com/uniwebadmin/uniweb/sdk/node

Install from Git (dist included):

npm install github:uniwebadmin/uniweb#main:sdk/node
# Local checkout: npm install /path/to/uniweb1/sdk/node

Create payment link:

import { Client } from 'uniweb';

const uniweb = new Client({
  apiKey: 'uw_test_your_key_here',
  apiSecret: 'uws_your_secret_here',
  mode: 'test',
});

const link = await uniweb.createPaymentLink({
  amount: 500,
  description: 'Order #123',
  customer_phone: '9876543210',
});
console.log(link.payment_url);

Methods (POST body action matches openapi.json): createPaymentLink, checkStatus, createRefund, getBalance, listTransactions, listRefunds, listPaymentLinks, getPaymentLink. Write calls send a unique Idempotency-Key header automatically. The SDK never logs your API secret.

Error handling — read stable error_code:

// PHP
try {
    $link = $uniweb->createPaymentLink(['amount' => 500]);
} catch (\UniWeb\Client\Exception\ApiException $e) {
    // $e->errorCode e.g. amount_out_of_range
}

// Node
try {
  await uniweb.createPaymentLink({ amount: 500 });
} catch (err) {
  if (err.errorCode) console.error(err.errorCode);
}

Webhook verify — header X-UniWeb-Signature = HMAC-SHA256(raw JSON body, signing secret from API Settings):

// PHP SDK
use UniWeb\Client\Webhook;
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_UNIWEB_SIGNATURE'] ?? '';
if (!Webhook::verifySignature($raw, $sig, $signingSecret)) {
    http_response_code(401); exit;
}

// Node SDK — use raw body string, not parsed JSON
import { verifySignature } from 'uniweb';
const ok = verifySignature(rawBody, req.headers['x-uniweb-signature'], signingSecret);

During signing-secret rotation, pass the previous secret as the optional fourth argument (PHP) or third optional param (Node) for a 48-hour grace window — same as the raw HMAC examples in Webhooks above.

Test vs Live Mode

Test Mode
  • Key prefix: uw_test_…
  • Available immediately after signup — no KYC
  • No real money; checkout shows UniWeb Test Mode banner
  • Webhooks fire with test transactions
  • Rate limit: 120 requests/minute
Live Mode
  • Key prefix: uw_live_…
  • Requires completed KYC + live activation
  • Real INR settlement through UniWeb rails
  • Separate webhook deliveries and transaction scope
  • Never mix test keys with live customer flows

A test key cannot capture live payments. Responses include "mode": "test" or live-scoped data matching your key. Rotate keys from API Settings if compromised.

Error Codes

Every error response is JSON with stable error_code (for your code) and human-readable error (for logs). HTTP status matches the severity.

error_codeHTTPMessage
invalid_json 400 Request body must be valid JSON.
unknown_action 400 Unknown action. See API documentation for supported actions.
missing_credentials 401 X-API-Key and X-API-Secret headers are required.
auth_failed 401 Invalid API credentials or insufficient scope.
auth_invalid 401 Partner rejected credentials during refund processing. Contact UniWeb support.
origin_not_allowed 403 Origin not allowed for this API key.
mode_mismatch 403 Account is in Test Mode. Use a test API key or complete KYC for live operations.
not_found 404 Resource not found.
validation_error 400 One or more fields failed validation.
amount_out_of_range 400 Amount must be between 1 and 200000000.
description_too_long 400 Description is too long (max 255 characters).
missing_txn_id 400 txn_id is required.
missing_link_id 400 link_id is required.
missing_idempotency_key 400 Idempotency-Key header is required for this action.
idempotency_conflict 409 Idempotency-Key conflict — different request body or an identical request is already in progress.
rate_limited 429 API rate limit exceeded. Retry after the Retry-After interval.
method_not_allowed 405 Only POST is supported.
refund_failed 400 Refund could not be processed.
refund_not_allowed 400 Refund is not allowed for this payment.
txn_not_refundable 404 Successful transaction not found for refund.
partner_unavailable 503 Payment partner is temporarily unavailable. Try again shortly.
internal_error 500 An internal error occurred. Support has been notified.

HTTP summary: 200 success · 400 bad request (incl. missing_idempotency_key) · 401 auth (auth_failed = your API key; auth_invalid = partner rejected refund) · 403 mode/origin · 404 not found · 405 method · 409 idempotency conflict · 429 rate limit (see Retry-After header, seconds) · 500 internal · 503 partner unavailable.

All Endpoints

All actions use POST to the same URL with an action field in the JSON body.

Create Payment Link

action: create_payment_link

{"action":"create_payment_link","amount":500,"description":"Order #123","customer_phone":"9876543210"}

Write — Idempotency-Key required. Returns payment_url.

Check Status

action: check_status

{"action":"check_status","txn_id":"TXN..."}

Read single transaction by txn_id.

List Transactions

action: list_transactions

{"action":"list_transactions","from":"2026-08-01","to":"2026-08-15","limit":20,"offset":0}

Optional date range (YYYY-MM-DD). Paginated — limit max 100.

Get Balance

action: get_balance

{"action":"get_balance"}

Collected and available settlement balance for current mode.

Create Refund

action: create_refund

{"action":"create_refund","txn_id":"TXN...","amount":100,"reason":"Customer request"}

Write — Idempotency-Key required. Omit amount for full refund.

List Refunds

action: list_refunds

{"action":"list_refunds","limit":20,"offset":0}

Paginated refund history.

List Payment Links

action: list_payment_links

{"action":"list_payment_links","limit":20,"offset":0}

Paginated links with view_count.

Get Payment Link

action: get_payment_link

{"action":"get_payment_link","link_id":"LNK..."}

Single link including payment_url.

Rate Limits & Idempotency

120 req/min

Per API credential. Short burst allowed.

429 + Retry-After

Use exponential backoff on rate-limit responses.

Reusing the same Idempotency-Key with an identical body returns the original response. Reusing with a different body returns 409 idempotency_conflict. Omitting the header on write actions returns 400 missing_idempotency_key. Keys are scoped per merchant + mode and stored for 72 hours in api_idempotency_keys.

Merchant onboarding API

Written exception (parked): there is no public REST action to create a merchant or poll KYC status. Onboarding stays on the UniWeb website — signup, admin invite, and KYC UI. When a named bank or fintech deal requires programmatic onboarding, this page and OpenAPI will be extended.