Backend Setup

The SDK requires a server-side endpoint to generate payment signatures. Signatures ensure that payment requests are authentic and have not been tampered with.

Never generate signatures on the client side. The merchant token must remain secret on your backend.


Signature Scheme

The signature is composed of two SHA-256 hashes joined by ///:

part1 = SHA256( clientTimeStamp + sid + amount + referenceNumber )
part2 = SHA256( clientTimeStamp + merchantToken + accountNumber + amount + referenceNumber )
signature = part1 + "///" + part2
Field Description
clientTimeStamp Epoch timestamp from the client (Date.now())
sid Session ID (UUID v4) generated by the client
amount Payment amount as string (e.g., "100.00")
referenceNumber Order/reference ID (empty string if not provided)
merchantToken Your secret merchant token (server-side only)
accountNumber Your merchant account number

Why amount must be a string: The two hash inputs are built by concatenating clientTimeStamp, sid, amount, and referenceNumber as text. The signature only matches if the exact same character sequence is used on the server as in the SDK. Numeric types can change how a value is rendered (for example 100 versus 100.00, or different fractional precision), which would change the hash. Passing amount as a string end-to-end keeps one fixed representation—including the decimal places and padding you intend—so the backend signature and the client payment request stay aligned.


Node.js (signature steps)

Below is a minimal illustration of the hashing and concatenation only. Your service should still validate amount, accountNumber, sid, and clientTimeStamp from the client before calling this.

const crypto = require('crypto');

const MERCHANT_TOKEN = process.env.MERCHANT_TOKEN || 'your-merchant-token';

function hash256(message) {
    return crypto.createHash('sha256').update(message).digest('hex');
}

const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function calculateSignature(clientTimeStamp, sid, amount, referenceNumber, accountNumber, merchantToken) {
    if (typeof sid !== 'string' || !UUID_V4_REGEX.test(sid)) {
        throw new Error('sid must be a valid GUID (UUID v4)');
    }
    const amountPart = (amount == null || amount === '' || amount === 'undefined') ? '' : String(amount);
    const token = merchantToken || MERCHANT_TOKEN;
    const ref = referenceNumber || '';

    const part1 = hash256(`${clientTimeStamp}${sid}${amountPart}${ref}`);
    const part2 = hash256(`${clientTimeStamp}${token}${accountNumber}${amountPart}${ref}`);
    return `${part1}///${part2}`;
}

Express server example

Validate the request body, then call calculateSignature with the same sid and clientTimeStamp the client used for the request.

const express = require('express');
const cors = require('cors');
const { calculateSignature } = require('./signature-hint'); // your module implementing the functions above

const app = express();
app.use(cors());
app.use(express.json());

app.post('/api/generate-signature', (req, res) => {
    const data = req.body;
    const amountStr = typeof data.amount === 'string' ? data.amount : String(data.amount);
    const amountNum = parseFloat(amountStr);
    if (amountStr && amountNum <= 0) {
        return res.status(400).json({ error: 'Invalid amount: amount must be greater than 0' });
    }

    const accountNumber = data.merchantAccountNumber || data.accountNumber;
    if (!accountNumber) {
        return res.status(400).json({ error: 'merchantAccountNumber or accountNumber is required' });
    }
    if (data.sid == null || data.sid === '') {
        return res.status(400).json({ error: 'sid is required (client-generated session ID)' });
    }
    if (data.clientTimeStamp == null) {
        return res.status(400).json({ error: 'clientTimeStamp is required (client-generated timestamp)' });
    }

    try {
        const signature = calculateSignature(
            data.clientTimeStamp,
            data.sid,
            amountStr,
            data.orderId || '',
            accountNumber,
            data.merchantToken || null
        );
        res.json({ signature });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.listen(process.env.PORT || 3001, () => {
    console.log('Signature server running on http://localhost:' + (process.env.PORT || 3001));
});

Standalone HTTP server (no Express)

A zero-dependency Node.js server ships at server-side/node/server.js. It uses a shared library and exposes:

POST /api/generate-signature
GET  / or GET /health

Request body:

{
    "amount": "100.00",
    "merchantAccountNumber": "ACCT-001",
    "orderId": "ORD-12345",
    "sid": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
    "clientTimeStamp": 1709912345678
}

Response:

{
    "signature": "a1b2c3...///d4e5f6..."
}

Run:

cd server-side/node
npm install
MERCHANT_TOKEN=your-token node server.js

Python (signature steps)

Minimal illustration of the same hashing and concatenation. Validate inputs in your HTTP handler before calling calculate_signature.

import hashlib
import os
import uuid
from typing import Optional

MERCHANT_TOKEN = os.environ.get("MERCHANT_TOKEN", "your-merchant-token")


def hash256(message: str) -> str:
    return hashlib.sha256(message.encode("utf-8")).hexdigest()


def _is_valid_uuid4(value: str) -> bool:
    if not value or not isinstance(value, str):
        return False
    try:
        u = uuid.UUID(value)
        return u.version == 4
    except (ValueError, AttributeError):
        return False


def calculate_signature(
    client_time_stamp,
    sid: str,
    amount: str,
    reference_number: str,
    account_number: str,
    merchant_token: Optional[str] = None,
) -> str:
    if not _is_valid_uuid4(sid):
        raise ValueError("sid must be a valid GUID (UUID v4)")
    amount_part = (
        ""
        if amount is None or amount == "" or amount in ("undefined", "None")
        else (amount if isinstance(amount, str) else str(amount))
    )
    token = merchant_token or MERCHANT_TOKEN
    ref = reference_number or ""
    part1 = hash256(f"{client_time_stamp}{sid}{amount_part}{ref}")
    part2 = hash256(f"{client_time_stamp}{token}{account_number}{amount_part}{ref}")
    return f"{part1}///{part2}"

Run the Python HTTP server

The repo includes a full HTTP server that uses the same library:

cd server-side/python
pip install -r requirements.txt
MERCHANT_TOKEN=your-token python server.py

POST /api/generate-signature uses the same JSON body and returns { "signature": "..." }.


Environment Variables

Variable Default Description
MERCHANT_TOKEN (use env in production) Your secret merchant token
PORT 3001 (Node) / see Python server HTTP server port

In production, always set MERCHANT_TOKEN via environment variable or a secrets manager. Never commit your real token to source control.


Integration Flow

Client (Browser)                          Your Backend
─────────────────                         ────────────
1. sid = generateSessionId()
2. clientTimeStamp = Date.now()
3. POST /api/generate-signature ──────▶  4. Validate inputs
   { amount, account, sid, ts }          5. Calculate signature
                                         6. Return { signature }
7. signature = response.signature  ◀─────
8. builder.setSignature(signature)
         .setSid(sid)
         .setClientTimeStamp(ts)
         ...
         .send()

Back to top

© Fawry Payment Solutions. All rights reserved.

This site uses Just the Docs, a documentation theme for Jekyll.