Verify Paymob callbacks with HMAC
Check Paymob's signature on every callback so fake payment messages are rejected.
How it works
Trigger: Webhook — Paymob transaction processed callback
- Receive the callback and the hmac value Paymob sends with it.
- Join the transaction fields in Paymob's documented order.
- Sign them with your HMAC secret using SHA-512.
- If the signatures match, continue your flow; if not, alert yourself.
Diagram
Trigger App Logic Code
Code
JavaScript used in the Code step — already included in the template below.
// Mode: Run Once for Each Item
// Builds the text Paymob signs for a "transaction processed" callback,
// using the field order from Paymob's HMAC documentation.
const o = $json.body?.obj || {};
const get = (path) => path.split(".").reduce((v, key) => (v == null ? v : v[key]), o);
const FIELDS = [
"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", "success",
];
return {
json: {
...$json,
signed: FIELDS.map((f) => String(get(f))).join(""),
receivedHmac: $json.query?.hmac || "",
},
};Build it
Pick your tool — same automation, step by step.
Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.
Show code (207 lines)
{
"name": "Verify Paymob callbacks with HMAC (Flowngn)",
"nodes": [
{
"parameters": {
"content": "## Verify Paymob callbacks with HMAC\nCheck Paymob's signature on every callback so fake payment messages are rejected.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
"height": 260,
"width": 340
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Setup notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-420,
-60
]
},
{
"parameters": {
"httpMethod": "POST",
"path": "paymob-secure",
"options": {}
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Paymob callback",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
],
"webhookId": "43ef56f7-3e64-497c-a29d-9f8e76872f38"
},
{
"parameters": {
"jsCode": "// Mode: Run Once for Each Item\n// Builds the text Paymob signs for a \"transaction processed\" callback,\n// using the field order from Paymob's HMAC documentation.\nconst o = $json.body?.obj || {};\nconst get = (path) => path.split(\".\").reduce((v, key) => (v == null ? v : v[key]), o);\n\nconst FIELDS = [\n \"amount_cents\", \"created_at\", \"currency\", \"error_occured\", \"has_parent_transaction\",\n \"id\", \"integration_id\", \"is_3d_secure\", \"is_auth\", \"is_capture\", \"is_refunded\",\n \"is_standalone_payment\", \"is_voided\", \"order.id\", \"owner\", \"pending\",\n \"source_data.pan\", \"source_data.sub_type\", \"source_data.type\", \"success\",\n];\n\nreturn {\n json: {\n ...$json,\n signed: FIELDS.map((f) => String(get(f))).join(\"\"),\n receivedHmac: $json.query?.hmac || \"\",\n },\n};",
"mode": "runOnceForEachItem"
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Build signed text",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
300,
0
]
},
{
"parameters": {
"action": "hmac",
"type": "SHA512",
"value": "={{ $json.signed }}",
"dataPropertyName": "hmac",
"secret": "YOUR_PAYMOB_HMAC_SECRET"
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "HMAC SHA-512",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
600,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose"
},
"conditions": [
{
"id": "c1",
"leftValue": "={{ $json.hmac === $json.receivedHmac }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Signature valid?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
900,
0
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a1",
"name": "verified",
"value": true,
"type": "boolean"
},
{
"id": "a2",
"name": "payment",
"value": "={{ $json.body.obj }}",
"type": "object"
}
]
},
"options": {}
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Verified payment",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1200,
0
]
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID",
"text": "=🚨 Paymob callback with an INVALID signature was rejected.\nTransaction: {{ $json.body?.obj?.id }}",
"additionalFields": {
"appendAttribution": false
}
},
"id": "43ef56f7-3e64-497c-a29d-9f8e76872f38",
"name": "Alert: bad signature",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1200,
220
]
}
],
"connections": {
"Paymob callback": {
"main": [
[
{
"node": "Build signed text",
"type": "main",
"index": 0
}
]
]
},
"Build signed text": {
"main": [
[
{
"node": "HMAC SHA-512",
"type": "main",
"index": 0
}
]
]
},
"HMAC SHA-512": {
"main": [
[
{
"node": "Signature valid?",
"type": "main",
"index": 0
}
]
]
},
"Signature valid?": {
"main": [
[
{
"node": "Verified payment",
"type": "main",
"index": 0
}
],
[
{
"node": "Alert: bad signature",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}In Make, create a new scenario and add these modules in order:
- Webhooks › Custom webhook
- Tools › Set variable / Iterator + Array aggregator (same logic as the Code step)
- Tools › Set variable using sha512(text; hex; your HMAC key)
- Router with two routes: “Signature valid?” (yes / no filters)
- If yesTools › Set multiple variables
- If noTelegram Bot › Send a Text Message or a Reply
- Every module run uses Make operations — the free plan includes a monthly quota.
- Make's built-in functions (map, sum, formatDate…) can replace most Code steps; for complex logic use a code module.
- Module and step names can differ slightly as Make and Zapier update their apps.
In Zapier, create a new Zap and add these steps in order:
- Webhooks by Zapier — Catch Hook
- Code by Zapier — Run JavaScript (paste the code from the Code section)
- Code by Zapier — Run JavaScript (crypto HMAC SHA-512)
- Paths by Zapier: “Signature valid?” (yes / no)
- If yesFormatter by Zapier (map the fields you need)
- If noTelegram — Send Message
- Zaps with more than two steps need a paid Zapier plan.
- Webhooks, Filters/Paths and Code by Zapier are premium features on some Zapier plans.
- Code by Zapier runs JavaScript: reuse the code shown above, reading inputs from inputData instead of $json.
- Module and step names can differ slightly as Make and Zapier update their apps.
Tips
- Find your HMAC secret in the Paymob dashboard under Settings.
- Put this check at the start of every flow that trusts Paymob callbacks.
Related flows
MENA & local appsEasy
Telegram alert for failed Paymob payments
Know instantly when a customer's payment fails, so you can call them and save the sale.
Paymob → Telegram2h/mo
MENA & local appsMedium
WhatsApp receipt for Paymob payments
When a Paymob payment succeeds, the customer gets a WhatsApp receipt instantly.
Paymob → WhatsApp5h/mo
MENA & local appsEasy
Log every Paymob payment in Google Sheets
Every Paymob transaction, successful or failed, becomes a row in your sheet.
Paymob → Google Sheets4h/mo