Authentication

Centiwise uses two complementary authentication mechanisms on every request:

MechanismApplies ToPurpose
OAuth 1.0 / RSA-SHA256All requestsProves the request came from your server
Control Hash (SHA-256)Payin onlyProves the payload has not been tampered with

OAuth 1.0 with RSA-SHA256

Every API call must include an Authorization header in the OAuth 1.0 format, signed using your RSA private key with the SHA-256 algorithm.

Required OAuth Header Fields

FieldValue
oauth_consumer_keyYour merchant_login
oauth_nonceA unique random string generated per request
oauth_signatureBase64-encoded RSA-SHA256 signature of the base string
oauth_signature_methodRSA-SHA256
oauth_timestampUnix timestamp (seconds since epoch)
oauth_version1.0

Example Authorization Header

Authorization: OAuth oauth_consumer_key="your_login",
  oauth_nonce="abc123xyz",
  oauth_signature="<base64signature>",
  oauth_signature_method="RSA-SHA256",
  oauth_timestamp="1714000000",
  oauth_version="1.0"

Node.js Implementation

const crypto = require('crypto');

function buildOAuthHeader(method, url, bodyParams, privateKey) {
  const nonce = crypto.randomBytes(16).toString('hex');
  const timestamp = Math.floor(Date.now() / 1000).toString();

  const oauthParams = {
    oauth_consumer_key:     process.env.CENTIWISE_LOGIN,
    oauth_nonce:            nonce,
    oauth_signature_method: 'RSA-SHA256',
    oauth_timestamp:        timestamp,
    oauth_version:          '2.0',
  };

  // Combine body params + OAuth params for signature base string
  const allParams = { ...bodyParams, ...oauthParams };

  const sortedParamString = Object.keys(allParams)
    .sort()
    .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(allParams[k])}`)
    .join('&');

  const baseString = [
    method.toUpperCase(),
    encodeURIComponent(url),
    encodeURIComponent(sortedParamString),
  ].join('&');

  const signer = crypto.createSign('RSA-SHA256');
  signer.update(baseString);
  oauthParams.oauth_signature = signer.sign(privateKey, 'base64');

  return 'OAuth ' + Object.entries(oauthParams)
    .map(([k, v]) => `${k}="${encodeURIComponent(v)}"`)
    .join(', ');
}

Python Implementation

import time, secrets, urllib.parse, hashlib
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def build_oauth_header(method, url, body_params, private_key_pem):
    nonce     = secrets.token_hex(16)
    timestamp = str(int(time.time()))

    oauth_params = {
        "oauth_consumer_key":     os.getenv("CENTIWISE_LOGIN"),
        "oauth_nonce":            nonce,
        "oauth_signature_method": "RSA-SHA256",
        "oauth_timestamp":        timestamp,
        "oauth_version":          "1.0",
    }

    all_params = {**body_params, **oauth_params}
    sorted_str = "&".join(
        f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(str(v), safe='')}"
        for k, v in sorted(all_params.items())
    )
    base_string = "&".join([
        method.upper(),
        urllib.parse.quote(url, safe=""),
        urllib.parse.quote(sorted_str, safe=""),
    ]).encode()

    private_key = serialization.load_pem_private_key(private_key_pem, password=None)
    signature   = private_key.sign(base_string, padding.PKCS1v15(), hashes.SHA256())
    encoded_sig = urllib.parse.quote(base64.b64encode(signature).decode(), safe="")

    oauth_params["oauth_signature"] = base64.b64encode(signature).decode()
    header_parts = ', '.join(
        f'{k}="{urllib.parse.quote(str(v), safe="")}"'
        for k, v in oauth_params.items()
    )
    return f"OAuth {header_parts}"

Control Hash (Payin Only)

Every Payin request must include a control field. This is a SHA-256 hash built from your request data and your merchant_control_key.

Formula

control = SHA256( merchant_login + client_orderid + amount + currency + merchant_control_key )

Concatenate all five values as plain strings with no separators, then hash.

Node.js

const crypto = require('crypto');

function buildControlHash(clientOrderId, amount, currency) {
  const raw = [
    process.env.CENTIWISE_LOGIN,
    clientOrderId,
    String(amount),
    currency,
    process.env.CENTIWISE_CONTROL_KEY,
  ].join('');

  return crypto.createHash('sha256').update(raw).digest('hex');
}

// Usage
const control = buildControlHash('ORD-001', 1000, 'KES');

Python

import hashlib, os

def build_control_hash(client_orderid: str, amount: int, currency: str) -> str:
    raw = (
        os.getenv("CENTIWISE_LOGIN") +
        client_orderid +
        str(amount) +
        currency +
        os.getenv("CENTIWISE_CONTROL_KEY")
    )
    return hashlib.sha256(raw.encode()).hexdigest()

# Usage
control = build_control_hash("ORD-001", 1000, "KES")
📝

Note: amount must be converted to a string before concatenation. Do not add any spaces, commas, or separators between values.



What’s Next

Did this page help you?