Agent skill

Paymob Webhooks Skill

Receive and verify Paymob webhook callbacks (transaction callbacks). Use when setting up Paymob webhook handlers, debugging HMAC-SHA512 signature verification, or handling payment transaction states like success, refund, void, and auth/capture from the Transaction Processed Callback.

Install this skill

npx skills add hookdeck/webhook-skills --skill paymob-webhooks


When to Use This Skill

  • How do I receive Paymob webhooks (transaction callbacks)?
  • How do I verify a Paymob HMAC signature?
  • Why is my Paymob HMAC verification failing?
  • How do I tell if a Paymob transaction succeeded, was refunded, or voided?
  • Understanding the difference between the Transaction Processed Callback (POST) and Transaction Response Callback (GET)

Verification (core)

Paymob does not use Standard Webhooks, and it does not sign the raw body. Instead it computes HMAC-SHA512 over a fixed, ordered concatenation of 20 specific fields (no separators), hex-encodes it, and sends the result as the hmac query parameter on the callback URL (?hmac=<hex>). Use the HMAC secret from your Paymob dashboard.

The 20 fields, in this exact order, are read from the transaction object (obj in the POST payload): 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.

Node (Transaction Processed Callback / POST, JSON body):

const crypto = require('crypto');

// obj = parsed request body `obj` field; hmacParam = req.query.hmac
function verifyPaymobHmac(obj, hmacParam, secret) {
  const s = obj.source_data || {};
  const signed = [
    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,
    s.pan, s.sub_type, s.type, obj.success,
  ].join(''); // JS renders booleans as "true"/"false", numbers as their digits
  const expected = crypto.createHmac('sha512', secret).update(signed).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(hmacParam || ''));
  } catch { return false; }
}

Python:

import hmac, hashlib

def verify_paymob_hmac(obj: dict, hmac_param: str, secret: str) -> bool:
    s = obj.get("source_data") or {}
    def v(x):  # JSON booleans must be lowercase "true"/"false"
        return "true" if x is True else "false" if x is False else str(x)
    signed = "".join(v(x) for x in [
        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"],
        s.get("pan"), s.get("sub_type"), s.get("type"), obj["success"],
    ])
    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha512).hexdigest()
    return hmac.compare_digest(expected, hmac_param or "")

For complete handlers with route wiring, state dispatch, and tests, see:

Callback Types & Transaction States

Paymob has one callback typeTRANSACTION — and no discrete event names. You determine the outcome by reading boolean fields on the transaction object:

FieldsTransaction state
success: true, is_refunded: false, is_voided: false, pending: falsePayment succeeded
success: false, error_occured: truePayment failed / declined
pending: trueAwaiting completion (e.g. 3-D Secure)
success: true, is_auth: true, is_capture: falseAuthorized (funds held, not captured)
is_capture: trueCaptured
is_refunded: trueRefunded
is_voided: trueVoided

Two callback kinds share these fields (configure both in the dashboard):

  • Transaction Processed Callback — server-to-server POST, JSON body { "type": "TRANSACTION", "obj": { … } }. Nested keys: obj.id, obj.order.id, obj.source_data.pan. This is the one to build your handler on.
  • Transaction Response Callback — client-redirect GET with flattened query params (id, order_id, source_data_pan, …). Same 20 fields, same order, same hmac param. See references/verification.md.

Environment Variables

PAYMOB_HMAC_SECRET=your_hmac_secret_here   # Dashboard → Settings → Account Info → HMAC

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 paymob --path /webhooks/paymob

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Aug 5, 2026

View on GitHub →