MyJamanot MyJamanot
Developers

Developer Documentation

Add "Pay with MyJamanot" to your own store's checkout page — buyer funds are held in escrow and released to you automatically once delivery is confirmed. This page is the complete integration guide, based on a real, working reference integration.

Overview

A merchant integration needs three things: your API URL, your API Key, and your API Secret. With those, plus a small drop-in HTML/CSS/JS block on your checkout page, your store gets a fully working "MyJamanot Escrow" payment option.

One rule to remember: your API Secret must never reach the browser. Every request is signed with it (see Server-side integration) — if it ever ships in page source or client-side JS, anyone who sees it can sign requests against your account. Same rule Stripe and every other payment gateway follows.

So the integration is two simple pieces:

  • Server-side (your backend) — uses your API Key + Secret to sign one request that creates the transaction. A few lines, in whatever language your backend runs.
  • Client-side (your checkout page) — plain drop-in HTML/CSS/JS for the payment button and popup. No secret involved, copy-paste as-is.

How it works

  1. Buyer reaches checkout

    Your checkout page renders normally, with a "MyJamanot" payment option next to your existing methods — pure HTML/CSS, no API call yet.

  2. Buyer picks a sub-method and submits the order

    Clicking "MyJamanot" opens a small popup (bKash / Nagad / Bank Transfer). The buyer sends money to your displayed number, enters the transaction ID, and submits the normal checkout form.

  3. Your backend creates the transaction

    On form submit, your server signs a request and calls POST /api/v1/merchant/transactions — including the payment info the buyer just gave you. MyJamanot auto-confirms it immediately, since you're vouching for that payment.

  4. MyJamanot returns a secure link

    The response includes a secure_link — a unique URL, already past commission agreement and payment confirmation. Show it on your order-confirmation page.

  5. Buyer manages the transaction on MyJamanot

    The buyer follows the link to a MyJamanot-hosted page — no login required. Just payment details and two buttons: Confirm (releases payment to you) and Refund (opens a dispute for MyJamanot to review). You don't need to build any of this.

  6. Your backend hears about completion (optional)

    Configure a webhook URL and MyJamanot notifies your backend as the transaction progresses, so you can update your own order status automatically.

Get your credentials

Log in as a seller and open API Access in your dashboard. One click issues your API Key + Secret — no admin approval needed. That page always shows your current API URL, Key, and Secret, and lets you regenerate the secret any time.

Keep the Secret in your server's environment config (e.g. .env) — never commit it, never send it to the browser.

Server-side integration

Every request carries four headers:

HeaderMeaning
X-Api-KeyYour public API Key (mk_...)
X-TimestampUnix seconds — rejected if more than 5 minutes off
X-NonceA random string, unique per request — replays are rejected
X-SignatureHMAC-SHA256 over the request, keyed by your secret
PHP — create a transaction when the buyer confirms checkout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function myjamanotCreateTransaction(array $payload, ?string $idempotencyKey = null): array {
    
$apiUrl    = 'https://your-myjamanot-domain.com';
    
$apiKey    = getenv('MYJAMANOT_API_KEY');     // from /seller/api-access
    
$apiSecret = getenv('MYJAMANOT_API_SECRET');  // never expose this to the browser

    
$path      = '/api/v1/merchant/transactions';
    
$rawBody   = json_encode($payload, JSON_UNESCAPED_SLASHES);
    
$timestamp = (string) time();
    
$nonce     = bin2hex(random_bytes(16));

    
$canonical = "POST\n{$path}\n{$timestamp}\n{$nonce}\n{$rawBody}";
    
$signature = hash_hmac('sha256', $canonical, $apiSecret);

    
$headers = [
        
'X-Api-Key: ' . $apiKey,
        
'X-Timestamp: ' . $timestamp,
        
'X-Nonce: ' . $nonce,
        
'X-Signature: ' . $signature,
        
'Content-Type: application/json',
    ];
    if (
$idempotencyKey) {
        
$headers[] = 'Idempotency-Key: ' . $idempotencyKey;
    }

    
$ch = curl_init($apiUrl . $path);
    
curl_setopt_array($ch, [
        
CURLOPT_CUSTOMREQUEST => 'POST',
        
CURLOPT_POSTFIELDS => $rawBody,
        
CURLOPT_HTTPHEADER => $headers,
        
CURLOPT_RETURNTRANSFER => true,
        
CURLOPT_TIMEOUT => 15,
    ]);
    
$response = curl_exec($ch);
    
curl_close($ch);

    return 
json_decode($response, true);
}

$result = myjamanotCreateTransaction([
    
'buyer_name'  => $fullName,
    
'buyer_email' => (preg_replace('/\D/', '', $phone) . '@yourstore.local'), // see Tip below
    
'buyer_phone' => '+88' . $phone,   // MyJamanot expects +8801XXXXXXXXX
    
'title'       => 'Order — ' . $orderId,
    
'amount'      => number_format($orderTotal, 2, '.', ''),
    
'commission_payer'  => 'BUYER',      // buyer bears the fee, on top of the order total
    
'payment_method'    => $subMethod,   // 'BKASH' | 'NAGAD' | 'BANK'
    
'payment_reference' => $trxId ?: null, // required unless BANK
], 'idem_' . $orderId);

if (
$result['success']) {
    
$secureLink = $result['data']['secure_link']; // show this on your confirmation page
} else {
    
// $result['errors'] holds field-level validation messages, if any
}
Tip — deterministic buyer email. MyJamanot resolves a buyer by email first, then rejects a phone number already tied to a different account. Always derive buyer_email from the phone number the same way every time (as above) — otherwise a repeat customer can trigger a "phone already registered" error.

Client-side widget

This part is genuinely copy-paste — no secret anywhere below.

HTML — payment method block + popup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class="mj-block" id="mjBlock">
  <div class="mj-block-icon">🛡️</div>
  <div class="mj-block-text">
    <div class="mj-block-title">Pay with MyJamanot <span class="mj-badge">Buyer Protected</span></div>
    <div class="mj-block-sub">Escrow-secured checkout — bKash, Nagad, or Bank Transfer.</div>
  </div>
  <button type="button" class="btn btn-outline btn-sm" id="mjOpenBtn">Select</button>
  <input type="radio" name="payment" value="myjamanot" id="pay-myjamanot" class="mj-hidden-radio">
</div>

<div class="mj-modal-backdrop" id="mjModalBackdrop"></div>
<div class="mj-modal" id="mjModal" role="dialog" aria-modal="true">
  <div class="mj-modal-header">🛡️ Pay with MyJamanot
    <button type="button" id="mjCloseBtn">✕</button>
  </div>
  <div class="mj-modal-body">
    <div class="mj-sub-options">
      <label class="mj-sub-option"><input type="radio" name="mj_sub" value="bkash"> 📱 bKash</label>
      <label class="mj-sub-option"><input type="radio" name="mj_sub" value="nagad"> 💰 Nagad</label>
      <label class="mj-sub-option"><input type="radio" name="mj_sub" value="bank"> 🏦 Bank Transfer</label>
    </div>
    <!-- bKash/Nagad number + TrxID input, shown per sub-method -->
  </div>
  <div class="mj-modal-footer">
    <button type="button" id="mjCancelBtn">Cancel</button>
    <button type="button" id="mjConfirmBtn">Confirm & Place Order</button>
  </div>
</div>

<input type="hidden" name="myjamanot_sub_method" id="myjamanotSubMethodField">
<input type="hidden" name="myjamanot_ref" id="myjamanotRefField">
JS — popup open/close and form field wiring (no API calls)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(function () {
  const mjOpenBtn = document.getElementById('mjOpenBtn');
  const backdrop = document.getElementById('mjModalBackdrop');
  const modal = document.getElementById('mjModal');
  const mjRadio = document.getElementById('pay-myjamanot');

  mjOpenBtn.addEventListener('click', () => {
    backdrop.classList.add('is-open');
    modal.classList.add('is-open');
  });
  const closeModal = () => {
    backdrop.classList.remove('is-open');
    modal.classList.remove('is-open');
  };
  document.getElementById('mjCloseBtn').addEventListener('click', closeModal);
  document.getElementById('mjCancelBtn').addEventListener('click', closeModal);

  document.getElementById('mjConfirmBtn').addEventListener('click', () => {
    const chosen = document.querySelector('[name="mj_sub"]:checked');
    if (!chosen) return alert('Please choose a payment method.');
    document.getElementById('myjamanotSubMethodField').value = chosen.value;
    document.getElementById('myjamanotRefField').value =
      document.getElementById('mjRef' + chosen.value).value.trim();
    mjRadio.checked = true;
    closeModal();
    // Your normal checkout form submits from here — the server-side
    // snippet above runs when it's received.
  });
})();

Style the .mj-* classes above to match your own brand — plain CSS, roughly 150 lines for the block, modal, and sub-option states.

Seller trust badge (optional)

Buyers are more likely to complete checkout when they can see why the payment option is trustworthy, not just that it exists. MyJamanot's public trust profile — the same verification badges and track record shown on your own /profile/{uuid} page — is available as a plain, unauthenticated GET endpoint, so you can pull your own account's status straight into the payment block:

Seller trust profile — no API key/signature needed, it's public data
1
2
3
4
5
6
7
8
9
10
11
12
GET https://your-myjamanot-domain.com/api/v1/users/{your_seller_uuid}/public-profile

{
  "success": true,
  "data": {
    "uuid": "9dc1dfeb-...",
    "name": "NOIR Shop",
    "role": "SELLER",
    "badges": ["TRUSTED", "IDENTITY_VERIFIED", "BUSINESS_VERIFIED", "TRADE_LICENSE_VERIFIED", "ADDRESS_VERIFIED"],
    "stats": { "total": 3, "successful": 3, "success_rate": 100, "review_count": 0, "average_rating": 0 }
  }
}

Your seller UUID is on your own MyJamanot account — Account → Profile → "Public Profile" link, the UUID in that URL. Fetch it once per page load, server-side (no CORS, and no risk of a slow/unreachable call blocking checkout if you fail closed like this):

PHP — fetch and interpret the trust data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function myjamanotGetSellerTrust(string $sellerUuid): ?array {
    
$ch = curl_init('https://your-myjamanot-domain.com/api/v1/users/' . rawurlencode($sellerUuid) . '/public-profile');
    
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
curl_setopt($ch, CURLOPT_TIMEOUT, 3);       // never let a slow trust-badge
    
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); // fetch hold up checkout

    
$responseBody = curl_exec($ch);
    
$ok = curl_errno($ch) === 0 && curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200;
    
curl_close($ch);
    if (!
$ok) {
        return 
null; // degrade silently — just don't show the badge
    
}

    
$decoded = json_decode((string) $responseBody, true);
    return 
$decoded['success'] ?? false ? $decoded['data'] : null;
}

$trust = myjamanotGetSellerTrust('your-seller-uuid');
$verifiedDocs = $trust ? count(array_intersect($trust['badges'], [
    
'IDENTITY_VERIFIED', 'BUSINESS_VERIFIED', 'TRADE_LICENSE_VERIFIED', 'ADDRESS_VERIFIED',
])) : 
0;
$isTrusted = $trust && in_array('TRUSTED', $trust['badges'], true);
PHP — drop this inside the .mj-block from the previous section
1
2
3
4
5
6
7
8
9
10
<?php if ($trust !== null): ?>
  <div class="mj-seller-trust <?= $isTrusted ? 'is-verified' : 'is-unverified' ?>">
    <?php if ($isTrusted): ?>
      ✓ Verified & Trusted Seller — <?= $verifiedDocs ?>/4 Document Verified
      · <?= (int) $trust['stats']['success_rate'] ?>% Success Rate
    <?php else: ?>
      ⚠ This seller is not fully verified yet — <?= $verifiedDocs ?>/4 Document Verified
    <?php endif; ?>
  </div>
<?php endif; ?>

This is exactly what the reference NOIR Shop integration does — nothing here is required for checkout to work, it only makes the existing payment option more convincing.

Show the secure link on order confirmation

PHP — order confirmation page
1
2
3
4
5
6
7
8
<?php if ($order['payment_method'] === 'myjamanot' && $order['myjamanot_secure_link']): ?>
  <div class="mj-confirm-panel">
    🛡️ Your payment is protected by MyJamanot Escrow
    <a href="<?= e($order['myjamanot_secure_link']) ?>" target="_blank">
      View your secure payment →
    </a>
  </div>
<?php endif; ?>

That link takes the buyer straight to a MyJamanot-hosted page — just payment details and two buttons (Confirm / Refund), no commission math. No login needed: the link itself is the credential.

Webhooks (optional)

Register a webhook URL from your API Access settings to have your backend notified as a transaction progresses — payment.confirmed, escrow.funded, transaction.released, transaction.disputed, and more. Each delivery is signed:

Verifying a webhook
1
2
3
4
5
6
7
$expected = 'sha256=' . hash_hmac('sha256', $rawRequestBody, $webhookSecret);
if (!
hash_equals($expected, $_SERVER['HTTP_X_MYJAMANOT_SIGNATURE'])) {
    
http_response_code(401);
    exit;
}
$event = json_decode($rawRequestBody, true);
// $event['event'], $event['transaction_id'], $event['data'], $event['timestamp']

Failed deliveries retry automatically, up to 5 attempts.

Summary

What you needWhere it's used
API URLYour MyJamanot domainYour backend's API calls
API Keymk_..., publicBackend X-Api-Key header
API Secretprivate — from API AccessBackend only — HMAC signing
HTML/CSS/JSdrop-in widget aboveYour checkout page's UI
Webhook Secretoptional, privateBackend — verifying webhook signatures

Ready to integrate?

Log in as a seller and get your API Key + Secret in under a minute.

Go to API Access