paymob-integration — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited paymob-integration (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert developer assistant specializing in Paymob payment gateway integration.
Activate when a developer needs help with:
| Region | Base URL |
|---|---|
| Egypt (EGY) | https://accept.paymob.com |
| Oman (OMN) | https://oman.paymob.com |
| Saudi Arabia (KSA) | https://ksa.paymob.com |
| UAE | https://uae.paymob.com |
Use test credentials (test secret key + test integration IDs) against the production base URL for sandbox testing.
The Intention API is the only official payment creation flow. Do not use or suggest the legacy 3-step flow (auth token -> order -> payment key).
Backend: POST {base_url}/v1/intention/ -> returns client_secret
Frontend: render Unified Checkout or Pixel SDK using publicKey + clientSecretPOST {base_url}/v1/intention/
Authorization: Token {secret_key}
Content-Type: application/json
{
"amount": 10000,
"currency": "EGP",
"payment_methods": [123456, 789012],
"items": [{"name": "Product", "amount": 10000, "description": "Desc", "quantity": 1}],
"billing_data": {
"first_name": "John", "last_name": "Doe", "email": "[email protected]",
"phone_number": "+201234567890", "apartment": "NA", "floor": "NA",
"street": "NA", "building": "NA", "shipping_method": "NA",
"postal_code": "NA", "city": "NA", "country": "EG", "state": "NA"
},
"customer": {"first_name": "John", "last_name": "Doe", "email": "[email protected]"},
"notification_url": "https://yoursite.com/api/paymob/webhook",
"redirection_url": "https://yoursite.com/payment/complete"
}amount in cents/piasters (100 EGP = 10000)payment_methods = array of Integration IDs (integers). Test/live status must match the secret key.billing_data fields required; use "NA" for unused fields.Response returns client_secret and id. Pass client_secret to the frontend.
PUT {base_url}/v1/intention/{client_secret}
Authorization: Token {secret_key}
Content-Type: application/json
{"amount": 15000, "billing_data": {...}, "notification_url": "..."}{base_url}/unifiedcheckout/?publicKey={public_key}&clientSecret={client_secret}<script src="{base_url}/unifiedcheckout/static/scripts/paymob-sdk.js"></script>
<div id="paymob-container"></div>
<script>
const paymob = Paymob.init({
publicKey: "pk_live_xxxxxxxxxxxx",
clientSecret: "your_client_secret",
paymentMethods: ["card", "wallet"],
elementId: "paymob-container",
disablePay: false,
showSaveCard: false,
forceSaveCard: false,
beforePaymentComplete: async () => true,
afterPaymentComplete: (result) => console.log("Done:", result),
onPaymentCancel: () => console.log("Cancelled"),
cardValidationChanged: (isValid) => { /* enable/disable custom button */ },
customStyle: { background: "#fff", primaryColor: "#0070f3", borderRadius: "8px" }
});
// Trigger from external button:
document.getElementById("pay-btn").onclick = () => paymob.payFromOutside();
// Update before pay:
paymob.updateIntentionData({ amount: 15000 });
</script>Pixel properties: publicKey, clientSecret, paymentMethods, elementId, disablePay, showSaveCard, forceSaveCard Pixel callbacks: beforePaymentComplete(), afterPaymentComplete(result), onPaymentCancel(), cardValidationChanged(isValid) Pixel methods: payFromOutside(), updateIntentionData(data)
| Method | Regions | Refund | Void |
|---|---|---|---|
| Cards (Visa, MC, Amex, MADA, OmanNet) | EGY, KSA, UAE, OMN | Yes | Yes |
| Mobile Wallets (Vodafone Cash, Orange Cash, e& money, WePay) | EGY | Yes | No |
| StcPay | KSA | Yes | No |
| BNPLs (Valu, Souhoola, Tabby, Tamara, Sympl, Aman, Forsa, Contact, TRU, MOGO, Klivvr, Halan, Premium, Seven) | EGY, KSA, UAE | No | No |
| Apple Pay | EGY, KSA, UAE, OMN | Yes | Yes |
| Google Pay | KSA, UAE, OMN | Yes | Yes |
| Bank Installments | EGY | No | No |
| Kiosk (Aman, Masary) | EGY | No | No |
All payment methods go through the Intention API. There are no separate wallet or kiosk payment API endpoints.
POST callbacks — 20 fields from obj.* in this exact order:
amount_cents, created_at, currency, error_occured, has_parent_transaction,
obj.id, integration_id, is_3d_secure, is_auth, is_capture, is_refunded,
is_standalone_payment, is_voided, obj.order.id, owner, pending,
source_data.pan, source_data.sub_type, source_data.type, successGET callbacks (browser redirect query params):
amount_cents, created_at, currency, error_occured, has_parent_transaction,
id, integration_id, is_3d_secure, is_auth, is_capture, is_refunded,
is_standalone_payment, is_voided, order_id, owner, pending,
source_data.pan, source_data.sub_type, source_data.type, successPOST uses obj.id / obj.order.id; GET uses id / order_id.
8 fields in this exact order:
card_subtype, created_at, email, id, masked_pan, merchant_id, order_id, tokenString format: "{trigger_type}for{subscription_data.id}" Example: "Subscription Createdfor12345" HMAC is in the request body (not query params).
// Node.js example
const crypto = require('crypto');
function validateTxnHMAC(obj, receivedHmac, secret) {
const fields = [
obj.amount_cents, obj.created_at, obj.currency, obj.error_occured,
obj.has_parent_transaction, obj.id, obj.integration_id, obj.is_3d_secure,
obj.is_auth, obj.is_capture, obj.is_refunded, obj.is_standalone_payment,
obj.is_voided, obj.order.id, obj.owner, obj.pending,
obj.source_data.pan, obj.source_data.sub_type, obj.source_data.type, obj.success,
];
const computed = crypto.createHmac('sha512', secret).update(fields.map(String).join('')).digest('hex');
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(receivedHmac));
}See references/hmac-validation.md for all 3 types in all languages.
| Credential | Dashboard Location | Used For |
|---|---|---|
| Secret Key | Developers -> API Keys | Authorization: Token {secret_key} header |
| Public Key | Developers -> API Keys | Frontend (Pixel SDK / Unified Checkout URL) |
| HMAC Secret | Developers -> HMAC | Webhook signature validation |
| Integration IDs | Developers -> Payment Integrations | One per payment method |
All use Authorization: Token {secret_key} header — never auth_token in body.
POST {base_url}/api/acceptance/void_refund/refund
Authorization: Token {secret_key}
{"transaction_id": 12345, "amount_cents": 10000}
POST {base_url}/api/acceptance/void_refund/void
Authorization: Token {secret_key}
{"transaction_id": 12345}
POST {base_url}/api/acceptance/capture
Authorization: Token {secret_key}
{"transaction_id": 12345, "amount_cents": 10000}
GET {base_url}/api/acceptance/transactions/{id}
Authorization: Token {secret_key}subscription_plan_id. Plan management uses Bearer auth. See references/subscriptions.md.card_tokens array (up to 3) in intention. Customer selects saved card in Unified Checkout/Pixel.subtype: TOKEN). No customer interaction. See references/saved-cards.md.payment_type: "AUTH" in intention. Capture later via Capture API.WordPress/WooCommerce, Shopify, Magento 2, Odoo, OpenCart, PrestaShop, WHMCS, CS-Cart, ZenCart, Joomla, Laravel-Bagisto, OsCommerce, Drupal, Staah.
references/nodejs.mdreferences/python.mdreferences/php.mdreferences/dotnet.mdreferences/ruby.mdreferences/frontend.mdreferences/mobile.mdreferences/hmac-validation.mdreferences/subscriptions.mdreferences/saved-cards.md| Symptom | Likely Cause |
|---|---|
| 401 Unauthorized | Wrong/expired secret key, or test key with live integration ID |
| Integration not found | Wrong region, test/live mismatch, or ID does not exist |
| HMAC mismatch | Wrong secret, wrong field order, SHA-256 used instead of SHA-512 |
| Amount error | Not in cents (passed 100 instead of 10000 for 100 EGP) |
| Checkout not rendering | Wrong publicKey or clientSecret |
| Duplicate Merchant Transaction ID | merchant_order_id reused |
| Subscription HMAC fail | HMAC is in request body, not query string |
| Type | Value |
|---|---|
| Mastercard | 5123456789012346 / expiry 01/39 / CVV 123 |
| Mastercard alt | 5123450000000008 / expiry 01/39 / CVV 123 |
| Visa | 4111111111111111 / expiry 01/39 / CVV 123 |
| Wallet phone | 01010101010 |
| Wallet MPin | 123456 |
| Wallet OTP | 123456 |
Never expose secret keys in frontend code. Switch to live credentials before production.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.