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.
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
-
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.
-
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.
-
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. -
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. -
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.
-
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:
| Header | Meaning |
|---|---|
X-Api-Key | Your public API Key (mk_...) |
X-Timestamp | Unix seconds — rejected if more than 5 minutes off |
X-Nonce | A random string, unique per request — replays are rejected |
X-Signature | HMAC-SHA256 over the request, keyed by your secret |
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
}
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.
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">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:
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):
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);
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
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:
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 need | Where it's used | |
|---|---|---|
| API URL | Your MyJamanot domain | Your backend's API calls |
| API Key | mk_..., public | Backend X-Api-Key header |
| API Secret | private — from API Access | Backend only — HMAC signing |
| HTML/CSS/JS | drop-in widget above | Your checkout page's UI |
| Webhook Secret | optional, private | Backend — 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