Verify Paymob callbacks with HMAC

Check Paymob's signature on every callback so fake payment messages are rejected.

Advanced Saves ~1 hours/month MENA & local appsBy Flowngn team

Works with n8n · Make · Zapier

How it works

Trigger: Webhook — Paymob transaction processed callback
  1. Receive the callback and the hmac value Paymob sends with it.
  2. Join the transaction fields in Paymob's documented order.
  3. Sign them with your HMAC secret using SHA-512.
  4. If the signatures match, continue your flow; if not, alert yourself.

Diagram

truefalsePaymob callbackWebhook{ }Build signed textCode#HMAC SHA-512Crypto⑂Signature valid?If✎Verified paymentEdit Fields (Set)TAlert: bad signatureTelegram
Trigger App Logic Code

Code

JavaScript used in the Code step — already included in the template below.

Build signed text · Code node
// 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.

Workflow JSON
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": []
}

Open in the playground to edit →

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