● Production Documentation

Paymora Payment Gateway

Universal hosted-payment integration documentation for PHP, Node.js, Python, Java, .NET, and any backend capable of sending HTTP redirects and receiving HTTPS webhooks.

Hosted checkout HMAC SHA256 Server-to-server webhook Language independent
Search results

Hosted Checkout

Redirect customers to the secure Paymora-hosted payment page.

Verified Webhooks

Confirm payments using signed server-to-server notifications.

Idempotent Processing

Protect your system from duplicate webhooks and double fulfilment.

1

Overview

How the Paymora hosted gateway works.

Paymora is a hosted payment gateway. Your backend creates an order, stores the payable amount, and redirects the customer to the Paymora payment page.

After payment processing, Paymora sends a signed server-to-server webhook to your backend. Your application must verify that webhook before marking an order as paid.

GET https://paymora.co.in/gateway/pay.php
Critical payment rule
Never confirm payment from the browser, success URL, fail URL, frontend JavaScript, query string, or customer session. Confirm payment only from a verified Paymora webhook.
2

Credentials and configuration

Required merchant configuration values.

Credential Purpose Exposure
client_id Identifies your Paymora merchant account when starting a payment. May be included in the payment request.
webhook_secret Used to verify webhook HMAC SHA256 signatures. Private. Backend only. Never expose it publicly.
webhook_url Public HTTPS endpoint that receives Paymora notifications. Public URL, but must verify every request.

Recommended environment variables

Environment
PAYMORA_PAYMENT_URL=https://paymora.co.in/gateway/pay.php
PAYMORA_CLIENT_ID=YOUR_CLIENT_ID
PAYMORA_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
PAYMORA_SUCCESS_URL=https://example.com/payment/success
PAYMORA_FAIL_URL=https://example.com/payment/failed
Do not place your webhook secret in HTML, JavaScript, mobile application code, public repositories, screenshots, or frontend API responses. Rotate any credential that has been accidentally disclosed.
3

Complete payment flow

Recommended end-to-end processing sequence.

1
Calculate the payable amount Read prices from your backend database and apply discounts or fees on the server.
2
Create the order Generate a unique order ID and save the final payable amount with status pending.
3
Redirect to Paymora Build the payment URL using the required parameters and redirect the customer.
4
Customer completes payment Paymora handles the hosted payment experience.
5
Paymora sends a webhook Your HTTPS webhook receives the transaction result.
6
Verify the signature Calculate HMAC SHA256 over the exact raw webhook body.
7
Validate and process Verify the order, status, amount, and duplicate state inside a database transaction.
8
Return OK Return HTTP 200 with the body OK after safe processing.
4

Payment request

Required parameters for starting a payment.

Parameter Type Required Description
client_id String Required Merchant/client ID issued by Paymora.
order_id String Required Unique merchant-generated order identifier.
amount Number Required Final backend-calculated payable amount.
success_url HTTPS URL Required Customer-facing redirect after payment flow.
fail_url HTTPS URL Required Customer-facing failure or expiry redirect.

Example payment URL

URL
https://paymora.co.in/gateway/pay.php?client_id=YOUR_CLIENT_ID&order_id=ORDER_123456&amount=1500.00&success_url=https%3A%2F%2Fexample.com%2Fpayment%2Fsuccess&fail_url=https%3A%2F%2Fexample.com%2Fpayment%2Ffailed
Generate order_id and calculate amount on the backend. Save both before redirecting the customer.
5

Webhook endpoint

Receive trusted server-to-server payment updates.

POST https://yourdomain.com/payments/paymora-webhook

Approved webhook payload

JSON
{
  "txn_id": "TXN2609YsH01Hs54Y6d587F8D73a1JYuvC",
  "order_id": "ORDER_123456",
  "amount": 1500,
  "status": "approved"
}

Payload fields

Field Type Description
txn_id String Unique Paymora transaction identifier.
order_id String Merchant order ID originally sent to Paymora.
amount Number Processed payment amount.
status String Payment processing status.

Supported statuses

Status Meaning Required action
approved Payment approved. Verify order and amount, then mark paid.
declined Payment declined. Do not provide paid services.
expired Payment session expired. Do not provide paid services.
6

Webhook signature verification

Authenticate incoming Paymora webhook requests.

Paymora sends the signature in this HTTP header:

HTTP Header
X-Genext-Signature: SIGNATURE_VALUE

The signature is calculated as:

Formula
HMAC-SHA256(raw_webhook_payload, webhook_secret)

Correct verification order

  1. Read the exact raw HTTP request body.
  2. Read the X-Genext-Signature header.
  3. Calculate HMAC SHA256 using the webhook secret.
  4. Use a constant-time comparison.
  5. Reject an invalid or missing signature.
  6. Only after verification, decode the JSON body.
Do not decode and then re-encode the JSON before calculating the signature. Whitespace, key order, number formatting, or line-ending changes can produce a different signature.
7

Webhook processing logic

Safe payment confirmation sequence.

Pseudocode
rawBody = readExactRawRequestBody()
receivedSignature = getHeader("X-Genext-Signature")

if receivedSignature is empty:
    return HTTP 401

expectedSignature = HMAC_SHA256_HEX(
    rawBody,
    WEBHOOK_SECRET
)

if constantTimeCompare(
    receivedSignature,
    expectedSignature
) is false:
    return HTTP 401

payload = parseJSON(rawBody)

validate:
    txn_id
    order_id
    amount
    status

begin database transaction

order = findAndLockOrder(payload.order_id)

if order does not exist:
    rollback
    return HTTP 404

if order.payment_status == "paid":
    commit
    return HTTP 200 "OK"

if payload.status != "approved":
    save gateway status
    commit
    return HTTP 200 "OK"

if payload.amount != order.payable_amount:
    mark for manual review
    commit
    return HTTP 200 "OK"

if payload.txn_id belongs to another order:
    rollback
    return HTTP 409

mark order paid
save txn_id
save paid_amount
deliver purchased service
insert audit ledger

commit

return HTTP 200 "OK"
8

Idempotency and duplicate protection

Prevent duplicate crediting and fulfilment.

Paymora may send the same webhook more than once. Your webhook must safely return OK without repeating a payment operation.

Required protections

  • Use a unique database constraint on order ID.
  • Use a unique database constraint on transaction ID.
  • Check whether the order is already paid.
  • Lock the order row while processing.
  • Use a database transaction.
  • Keep service fulfilment in the same transaction where possible.
First webhook: pending → paid → service delivered → OK.
Duplicate webhook: already paid → no further action → OK.
9

Amount verification

Compare the webhook amount with your saved order.

Always compare the verified webhook amount against the payable_amount saved before redirecting to Paymora.

Source Amount
Merchant database ₹1,500.00
Verified webhook ₹1,400.00
This is an amount mismatch. Do not mark the order paid, do not credit a wallet, and do not deliver the purchased service. Flag the transaction for manual reconciliation.

For reliable comparisons, use decimal money values or convert both amounts to integer paise.

10

PHP integration

Create a payment and process the webhook in PHP.

Create payment

PHP
<?php

define(
    'PAYMORA_PAYMENT_URL',
    'https://paymora.co.in/gateway/pay.php'
);

define(
    'PAYMORA_CLIENT_ID',
    getenv('PAYMORA_CLIENT_ID')
);

$orderId =
    'ORDER_' .
    date('YmdHis') .
    '_' .
    bin2hex(random_bytes(4));

$payableAmount = '1500.00';

/*
Save before redirecting:

order_id       = $orderId
payable_amount = $payableAmount
payment_status = pending
*/

$paymentUrl =
    PAYMORA_PAYMENT_URL .
    '?' .
    http_build_query([
        'client_id'   => PAYMORA_CLIENT_ID,
        'order_id'    => $orderId,
        'amount'      => $payableAmount,
        'success_url' =>
            'https://example.com/payment/success.php',
        'fail_url'    =>
            'https://example.com/payment/failed.php'
    ]);

header('Location: ' . $paymentUrl);
exit;

Webhook signature verification

PHP
<?php

$webhookSecret =
    getenv('PAYMORA_WEBHOOK_SECRET');

$rawPayload =
    file_get_contents('php://input');

$receivedSignature =
    $_SERVER['HTTP_X_GENEXT_SIGNATURE'] ?? '';

$expectedSignature = hash_hmac(
    'sha256',
    $rawPayload,
    $webhookSecret
);

if (
    $receivedSignature === '' ||
    !hash_equals(
        $expectedSignature,
        $receivedSignature
    )
) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($rawPayload, true);

if (
    !is_array($data) ||
    !isset(
        $data['txn_id'],
        $data['order_id'],
        $data['amount'],
        $data['status']
    )
) {
    http_response_code(400);
    exit('Invalid payload');
}

$txnId = trim((string)$data['txn_id']);
$orderId = trim((string)$data['order_id']);
$status = trim((string)$data['status']);
$paidAmount = (string)$data['amount'];

if (
    $txnId === '' ||
    $orderId === '' ||
    !is_numeric($paidAmount)
) {
    http_response_code(400);
    exit('Invalid fields');
}

/*
Use a database transaction here:

1. SELECT order FOR UPDATE.
2. Check whether already paid.
3. Verify status = approved.
4. Compare amount.
5. Save txn_id and paid_amount.
6. Mark paid.
7. Deliver service.
8. COMMIT.
*/

http_response_code(200);
exit('OK');
11

Node.js integration

Express example using a raw webhook request body.

Create payment

JavaScript
const crypto = require('crypto');
const express = require('express');

const app = express();

const paymentUrl =
    'https://paymora.co.in/gateway/pay.php';

app.post('/payments/create', async (req, res) => {
    const orderId =
        'ORDER_' +
        Date.now() +
        '_' +
        crypto.randomBytes(4).toString('hex');

    const amount = '1500.00';

    // Save orderId, amount and pending status first.

    const params = new URLSearchParams({
        client_id: process.env.PAYMORA_CLIENT_ID,
        order_id: orderId,
        amount: amount,
        success_url:
            'https://example.com/payment/success',
        fail_url:
            'https://example.com/payment/failed'
    });

    res.redirect(
        paymentUrl + '?' + params.toString()
    );
});

Webhook

JavaScript
app.post(
    '/payments/paymora-webhook',
    express.raw({
        type: 'application/json'
    }),
    async (req, res) => {
        const rawPayload = req.body;

        const received =
            req.get('X-Genext-Signature') || '';

        const expected = crypto
            .createHmac(
                'sha256',
                process.env.PAYMORA_WEBHOOK_SECRET
            )
            .update(rawPayload)
            .digest('hex');

        const receivedBuffer =
            Buffer.from(received, 'utf8');

        const expectedBuffer =
            Buffer.from(expected, 'utf8');

        if (
            receivedBuffer.length !==
            expectedBuffer.length ||
            !crypto.timingSafeEqual(
                receivedBuffer,
                expectedBuffer
            )
        ) {
            return res
                .status(401)
                .send('Invalid signature');
        }

        let payload;

        try {
            payload = JSON.parse(
                rawPayload.toString('utf8')
            );
        } catch (error) {
            return res
                .status(400)
                .send('Invalid JSON');
        }

        /*
        Validate fields and process inside
        a database transaction.
        */

        return res.status(200).send('OK');
    }
);
Register the webhook route with express.raw() before any global express.json() middleware that could parse the body.
12

Python integration

Flask create-payment and webhook examples.

Create payment

Python
import os
import secrets
import time

from urllib.parse import urlencode
from flask import Flask, redirect

app = Flask(__name__)

PAYMORA_PAYMENT_URL = (
    "https://paymora.co.in/gateway/pay.php"
)

@app.post("/payments/create")
def create_payment():
    order_id = (
        f"ORDER_{int(time.time())}_"
        f"{secrets.token_hex(4)}"
    )

    amount = "1500.00"

    # Save order before redirecting.

    parameters = {
        "client_id":
            os.environ["PAYMORA_CLIENT_ID"],
        "order_id": order_id,
        "amount": amount,
        "success_url":
            "https://example.com/payment/success",
        "fail_url":
            "https://example.com/payment/failed"
    }

    return redirect(
        PAYMORA_PAYMENT_URL +
        "?" +
        urlencode(parameters)
    )

Webhook

Python
import os
import hmac
import hashlib
import json

from flask import request, Response

@app.post("/payments/paymora-webhook")
def paymora_webhook():
    raw_payload = request.get_data(
        cache=False,
        as_text=False
    )

    received = request.headers.get(
        "X-Genext-Signature",
        ""
    )

    expected = hmac.new(
        os.environ[
            "PAYMORA_WEBHOOK_SECRET"
        ].encode("utf-8"),
        raw_payload,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(
        received,
        expected
    ):
        return Response(
            "Invalid signature",
            status=401
        )

    try:
        data = json.loads(raw_payload)
    except Exception:
        return Response(
            "Invalid JSON",
            status=400
        )

    # Validate and process in a transaction.

    return Response("OK", status=200)
13

Java integration

Spring Boot webhook signature verification.

Java
@PostMapping("/payments/paymora-webhook")
public ResponseEntity<String> webhook(
    @RequestBody byte[] rawPayload,
    @RequestHeader(
        value = "X-Genext-Signature",
        required = false
    ) String receivedSignature
) {
    try {
        if (
            receivedSignature == null ||
            receivedSignature.isEmpty()
        ) {
            return ResponseEntity
                .status(HttpStatus.UNAUTHORIZED)
                .body("Invalid signature");
        }

        String webhookSecret =
            System.getenv(
                "PAYMORA_WEBHOOK_SECRET"
            );

        Mac mac = Mac.getInstance(
            "HmacSHA256"
        );

        mac.init(
            new SecretKeySpec(
                webhookSecret.getBytes(
                    StandardCharsets.UTF_8
                ),
                "HmacSHA256"
            )
        );

        byte[] hash =
            mac.doFinal(rawPayload);

        StringBuilder hex =
            new StringBuilder();

        for (byte value : hash) {
            hex.append(
                String.format("%02x", value)
            );
        }

        byte[] expected =
            hex.toString().getBytes(
                StandardCharsets.UTF_8
            );

        byte[] received =
            receivedSignature.getBytes(
                StandardCharsets.UTF_8
            );

        if (
            !MessageDigest.isEqual(
                expected,
                received
            )
        ) {
            return ResponseEntity
                .status(HttpStatus.UNAUTHORIZED)
                .body("Invalid signature");
        }

        // Decode JSON only after verification.
        // Validate order, status and amount.
        // Process inside a transaction.

        return ResponseEntity.ok("OK");

    } catch (Exception exception) {
        return ResponseEntity
            .status(
                HttpStatus.INTERNAL_SERVER_ERROR
            )
            .body("Processing error");
    }
}
14

Success and failure pages

Customer-facing redirect page requirements.

Success page

The success page may display:

  • Payment confirmation is being processed.
  • The merchant order ID.
  • Current status read from the merchant database.
  • A refresh or automatic status-check option.
Reaching success_url does not prove payment was approved. The customer can manually open this page.

Failure page

The failure page may display:

  • Payment failed, cancelled, or expired.
  • A retry-payment option.
  • Merchant support details.

Do not update the order as paid from either redirect page.

15

Recommended database structure

Auditable payment-order storage.

SQL
CREATE TABLE payment_orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    order_id VARCHAR(100) NOT NULL UNIQUE,
    payable_amount DECIMAL(12,2) NOT NULL,
    paid_amount DECIMAL(12,2) NULL,
    txn_id VARCHAR(150) NULL UNIQUE,
    payment_status VARCHAR(30)
        NOT NULL DEFAULT 'pending',
    gateway_status VARCHAR(30) NULL,
    mismatch_reason VARCHAR(255) NULL,
    created_at DATETIME
        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    paid_at DATETIME NULL,
    updated_at DATETIME
        NOT NULL DEFAULT CURRENT_TIMESTAMP
        ON UPDATE CURRENT_TIMESTAMP
);

Before payment

Record
order_id        = ORDER_123456
payable_amount  = 1500.00
paid_amount     = NULL
txn_id          = NULL
payment_status  = pending

After verified approved webhook

Record
order_id        = ORDER_123456
payable_amount  = 1500.00
paid_amount     = 1500.00
txn_id          = TXN2609YsH01Hs54Y6d587F8D73a1JYuvC
payment_status  = paid
gateway_status  = approved
paid_at         = 2026-08-17 12:30:05
Use DECIMAL for stored money. Do not use FLOAT or DOUBLE for financial values.
16

Security requirements

Mandatory production safeguards.

  • Use HTTPS for all payment endpoints.
  • Store secrets in environment variables.
  • Never expose the webhook secret.
  • Read the exact raw webhook body.
  • Verify HMAC SHA256 signatures.
  • Use constant-time comparison.
  • Validate every webhook field.
  • Use prepared SQL statements.
  • Calculate amount on the backend.
  • Save amount before redirecting.
  • Verify the webhook amount.
  • Use database transactions.
  • Prevent duplicate processing.
  • Add unique order and transaction IDs.
  • Never trust browser payment status.
  • Maintain an audit trail.
If a client ID, webhook secret, API key, or database password is exposed, rotate it immediately. Removing it from a file does not make an already disclosed credential safe.
17

Testing checklist

Validate payment behavior before production.

Test Expected result
Approved payment Order is marked paid exactly once.
Duplicate webhook No duplicate credit or fulfilment.
Invalid signature HTTP 401; database remains unchanged.
Missing signature HTTP 401.
Invalid JSON HTTP 400.
Unknown order ID No payment update; event is logged.
Amount mismatch Order is not marked paid.
Declined payment No service is delivered.
Expired payment No service is delivered.
Database failure Transaction is rolled back.
Manual success-page access Order is not marked paid.
18

Troubleshooting

Common integration problems and solutions.

Webhook is not received

  • Confirm the webhook URL is configured with Paymora.
  • Use a publicly accessible HTTPS URL.
  • Check firewall, WAF, and hosting security rules.
  • Inspect server access and application logs.
  • Confirm the endpoint accepts POST requests.

Signature verification fails

  • Confirm the webhook secret is correct.
  • Read the exact raw request body.
  • Do not parse JSON before calculating HMAC.
  • Use SHA256 and hexadecimal output.
  • Read the X-Genext-Signature header.

Payment page works but order stays pending

  • Check whether the webhook reached your server.
  • Check signature-verification logs.
  • Verify the order ID exists in your database.
  • Verify the webhook amount matches.
  • Check transaction rollback or database errors.

Duplicate wallet credit

  • Add a unique constraint to transaction ID.
  • Check the paid state before crediting.
  • Lock the order row during processing.
  • Credit the wallet inside the same transaction.
19

Production checklist

Final review before enabling live payments.

  • Production payment URL configured.
  • HTTPS is enabled.
  • Client ID configured securely.
  • Webhook secret configured securely.
  • Unique order ID implemented.
  • Order saved before redirect.
  • Final payable amount stored.
  • Public webhook configured.
  • Raw webhook body is read.
  • Signature is verified.
  • Payload fields are validated.
  • Approved status is checked.
  • Order ID is verified.
  • Amount is compared.
  • Duplicate processing is prevented.
  • Transaction ID is saved.
  • Paid amount is saved.
  • Database transaction is used.
  • Webhook returns OK.
  • Logging is enabled.
  • Invalid-signature test completed.
  • Amount-mismatch test completed.
  • Duplicate-webhook test completed.
  • Secrets are excluded from source control.
Payment confirmation is complete only after your backend has verified the signature, validated the order, checked the amount, prevented duplicates, and committed the database transaction.