Transaction Callbacks

A full guide to your transaction callbacks.

📘

What is the transaction callback?

When your customer performs a transaction or any action related to some transaction "Void, Refund, Pay, etc..." regardless of the type of the transaction, a webhook request is initiated from our end to defined endpoints in your web app to inform you with the state of the transaction.

There are two types of transaction callbacks, transaction processed callback and transaction response callback.

To learn how to add your callback endpoints, please check the payment integration guide.

You can consider these callbacks as a receipt for your transactions.

Transaction Processed Callback

It's an URL for an endpoint in your web app, on which you'd receive callbacks as a notification after performing any payment process from your customer side, you would receive a JSON object in POST request which contains a value by which you can know about your payments such as the status of the transaction (success/declined), the order ID related to this transition, the transaction ID and much other information related to your transaction, here's a sample request similar to the one you should receive on your transaction processed callback endpoint.

{
  "obj": {
    "id": 2556706,
    "pending": false,
    "amount_cents": 100,
    "success": true,
    "is_auth": false,
    "is_capture": false,
    "is_standalois_capturene_payment": true,
    "is_voided": false,
    "is_refunded": false,
    "is_3d_secure": true,
    "integration_id": 6741,
    "profile_id": 4214,
    "has_parent_transaction": false,
    "order": {
      "id": 4778239,
      "created_at": "2020-03-25T18:36:05.494685",
      "delivery_needed": true,
      "merchant": {
        "id": 4214,
        "created_at": "2019-09-22T18:32:56.764441",
        "phones": [
          "01032347111"
        ],
        "company_emails": [
          "[email protected]"
        ],
        "company_name": "Accept Payments",
        "state": "",
        "country": "EGY",
        "city": "",
        "postal_code": "",
        "street": ""
      },
      "collector": {
        "id": 115,
        "created_at": "2019-06-29T00:48:26.910433",
        "phones": [],
        "company_emails": [],
        "company_name": "logix - test",
        "state": "Heliopolis",
        "country": "egypt",
        "city": "cairo",
        "postal_code": "123456",
        "street": "Marghany"
      },
      "amount_cents": 2000,
      "shipping_data": {
        "id": 2558893,
        "first_name": "abdulrahman",
        "last_name": "Khalifa",
        "street": "Wadi el Nile",
        "building": "5",
        "floor": "11",
        "apartment": "1565162",
        "city": "Cairo",
        "state": "Cairo",
        "country": "EG",
        "email": "[email protected]",
        "phone_number": "01011994353",
        "postal_code": "",
        "extra_description": " ",
        "shipping_method": "UNK",
        "order_id": 4778239,
        "order": 4778239
      },
      "shipping_details": {
        "id": 1401,
        "cash_on_delivery_amount": 0,
        "cash_on_delivery_type": "Cash",
        "latitude": null,
        "longitude": null,
        "is_same_day": 0,
        "number_of_packages": 1,
        "weight": 1,
        "weight_unit": "Kilogram",
        "length": 1,
        "width": 1,
        "height": 1,
        "delivery_type": "PUD",
        "return_type": null,
        "order_id": 4778239,
        "notes": "im so tired",
        "order": 4778239
      },
      "currency": "EGP",
      "is_payment_locked": false,
      "is_return": false,
      "is_cancel": false,
      "is_returned": false,
      "is_canceled": false,
      "merchant_order_id": null,
      "wallet_notification": null,
      "paid_amount_cents": 100,
      "notify_user_with_email": false,
      "items": [],
      "order_url": "https://accept.paymobsolutions.com/i/nYWD",
      "commission_fees": 0,
      "delivery_fees_cents": 0,
      "delivery_vat_cents": 0,
      "payment_method": "tbc",
      "merchant_staff_tag": null,
      "api_source": "OTHER",
      "pickup_data": null,
      "delivery_status": []
    },
    "created_at": "2020-03-25T18:39:44.719228",
    "transaction_processed_callback_responses": [],
    "currency": "EGP",
    "source_data": {
      "pan": "2346",
      "type": "card",
      "sub_type": "MasterCard"
    },
    "api_source": "IFRAME",
    "terminal_id": null,
    "is_void": false,
    "is_refund": false,
    "data": {
      "acq_response_code": "00",
      "avs_acq_response_code": "Unsupported",
      "klass": "VPCPayment",
      "receipt_no": "008603626261",
      "order_info": "[email protected]",
      "message": "Approved",
      "gateway_integration_pk": 6741,
      "batch_no": "20200325",
      "card_num": null,
      "secure_hash": "832F4673452F9538CCD57D6B07B74183A0EEB1BEF7CA58704E31B244E8366549",
      "avs_result_code": "Unsupported",
      "card_type": "MC",
      "merchant": "TEST999999EGP",
      "created_at": "2020-03-25T16:40:37.127504",
      "merchant_txn_ref": "6741_572e773a5a0f55ff8de91876075d023e",
      "authorize_id": "626261",
      "currency": "EGP",
      "amount": "100",
      "transaction_no": "2090026774",
      "txn_response_code": "0",
      "command": "pay"
    },
    "is_hidden": false,
    "payment_key_claims": {
      "lock_order_when_paid": true,
      "integration_id": 6741,
      "billing_data": {
        "email": "[email protected]",
        "building": "8028",
        "apartment": "803",
        "street": "Ethan Land",
        "country": "CR",
        "state": "Utah",
        "last_name": "Nicolas",
        "first_name": "Clifford",
        "postal_code": "01898",
        "extra_description": "NA",
        "phone_number": "+86(8)9135210487",
        "floor": "42",
        "city": "Jaskolskiburgh"
      },
      "order_id": 4778239,
      "user_id": 4705,
      "pmk_ip": "197.57.37.135",
      "exp": 1585157836,
      "currency": "EGP",
      "amount_cents": 100
    },
    "error_occured": false,
    "is_live": false,
    "other_endpoint_reference": null,
    "refunded_amount_cents": 0,
    "source_id": -1,
    "is_captured": false,
    "captured_amount": 0,
    "merchant_staff_tag": null,
    "owner": 4705,
    "parent_transaction": null
  },
  "type": "TRANSACTION"
}
1002020-03-25T18:39:44.719228EGPfalsefalse25567066741truefalsefalsefalsetruefalse47782394705false2346MasterCardcardtrue
6965eb228a2ee5003f9dc01528d68271fdbeae7af0e5bbb1d4915cecff675c2fcb3f08aec78e5859e198ca2b1e53c622a7b5ab7dcb9d15b6ab051a25d1ea1a74

This is a sample transaction processed callback for a successful transaction, you don't have to use all these keys, let's describe some of the important keys in this object, check the following table:

KeyDescription
id The ID of this transaction, you can check it from your Accept portal, transaction tab.
pending A boolean-valued key indicating the state of this transaction, it would be true in one of these cases:
Card Payments: The customer has been redirected to the issuing bank page to enter his OTP.
Kiosk Payments: A payment reference number was generated and it is pending to be paid.
Cash Payments: Your cash payment is ready to be collected and the courier is on his way to collect it from your customer.
success A boolean-valued key indicating the status of the transaction whether it was successful or not, it would be true if your customer has successfully performed his payment.
is_auth A boolean-valued key indicating if this was an authorized transaction, learn more about auth/cap transactions.
is_capture A boolean-valued key indicating if this was a capture transaction, learn more about auth/cap transactions.
amount_cents An integer field indicating the amount that was paid to this transaction, it might be different than the original order price, and it is in cents.
is_voided A boolean-valued key indicating if this transaction was voided or not, learn more about void and refund transaction.
is_refunded A boolean-valued key indicating if this transaction was refunded or not, learn more about void and refund transaction.
is_3d_secure A boolean-valued key indicating if this transaction was 3D secured or not, learn more about the 3D and moto transactions.
integration_id An integer field referring to the integration ID that this transaction was performed through, learn more about your payment integrations.
order This is a JSON sub-object contains the order data related to this transaction.
order[id] A boolean-valued key of the order sub-object indicating the ID of this order at Accept's database, you can find it in your Accept portal, order tab.
order[created_at] A string-valued key of the order sub-object indicating the date and time that this order was created at.
order[delivery_needed] A boolean-valued key of the order sub-object indicating if this order needed to be delivered through Accept's Order Delivery Services.
Learn more about Accept's Order Delivery Services.
order[amount_cents] An integer-valued key of the order sub-object indicating the original price of this order, and it is in cents.
order[shipping_data} This is a sub-object from the order sub-object filled with the shipping data that this order might be delivered to if this order needed to be delivered.
This data was originally prefilled in your order registration request.
order[merchant_order_id] A string-valued key of the order sub-object indicating a reference to this order in your database, the value of this key was provided in your order registration request.
order[paid_amount_cents] An integer-valued key of the order sub-object indicating the amount paid to this order and it should be the same as the transaction's amount_cents key's value.
currency A string-valued key referencing to the currency of the payment integration that this transaction was performed through.

Transaction Response Callback

After performing any payment you should redirect your customer again to your platform with a proper message clarifying the state of the payment he just performed.
The transaction response callback is a set of query parameters that we append it to your endpoint then redirect your customer to it after performing his payment then you can parse it and upon its values, you'd show your customer a proper message.
These query params are the same keys found in the transaction processed callback JSON object.

Useful Testing Tools

  1. In order to receive your transaction callbacks, your app should be deployed on a public accessed endpoint, so if you were developing your app on your local machine and you want to test receiving the callbacks you might need to set a secure introspectable tunnel to your localhost webhook development, one of the recommended tools is ngrok, generate URL and add it to be your callback URL.

  2. If you didn't receive the callbacks and you want to debug the error, you can use one of these HTTP requests inspection tools "webhook", "requestbin", "requestwatch".
    These tools will generate endpoints URLs that you can add it to your transaction processed/response callbacks to be able if you receiving your callbacks or not after performing any payment.

Transaction Processed Callbacks vs Transaction Response Callbacks

Transaction Processed CallbacksTransaction Response Callbacks
Request TypePOSTGET
Request ContentJSONQuery Param
DirectionServer SideClient Side

❗️

Caution!

In order to verify that these requests are received from Accept's endpoint, you have to implement HMAC authentication to validate the source of the callbacks.
The following point will learn you more about HMAC authentication.

HMAC Authentication

Accept callbacks rely on HMAC authentication to verify Accept's identity and integrity of its data.
Every and each callback invoked from Accept's server-side has its own HMAC validation, and they all calculated with the same methodology, once you implemented one of them you can implement the others.

Main Method

Whenever you receive a callback from Accept's end, You will receive a value of the HMAC related to the data received in the request in a query param called hmac, you should calculate an HMAC value equal to the HMAC sent with the received callback, in order to calculate an HMAC similar to the one you received, prepare your endpoint to perform the following:

  1. Sort the data received by key Lexicographical order.

  2. Depending on the type of the received callback, concatenate the values of the keys/params in one string, for any transaction callback, the data fields that should be taken in the concentrations are:

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

The keys/params should be in the same order shown in the above text.

  1. Calculate the hash of the concatenated string using SHA512 and your HMAC secret, found in the profile tab in your dashboard.

  2. Convert the resultant HMAC is Hex (base 16) lowercase.

  3. Now compare both HMAC values, the one you received with the sent request and the one you calculated out of this request if both are equal, you can safely save this data and use it in your system.

Transaction Processed/Response Callback:

For the processed callbacks, you should concatenate a string from the mentioned fields with the same order.
So, if we considered the up-mentioned sample transaction processed callback sample, the resultant string should be like:

1002020-03-25T18:39:44.719228EGPfalsefalse25567066741truefalsefalsefalsetruefalse47782394705false2346MasterCardcardtrue

And if you considered the up-mentioned transaction processed callback sample, the HMAC related to it should be:

6965eb228a2ee5003f9dc01528d68271fdbeae7af0e5bbb1d4915cecff675c2fcb3f08aec78e5859e198ca2b1e53c622a7b5ab7dcb9d15b6ab051a25d1ea1a74

Saved Card Token Object

If your users intended to save their cards you should receive an object like this with the transaction processed callback in which you can find the token related to the user card.

Sample Token Object:

{
“type":"TOKEN",
“obj” : {
    "id": 10,
    "token": "d20d94...8000687835c3f1a9da9",
    "masked_pan": "xxxx-xxxx-xxxx-2346",
    "merchant_id": 1,
    "card_subtype": "MasterCard",
    "created_at": "2016-12-26T06:49:18.017207Z",
    "email": "[email protected]",
    "order_id": “55"
	}
}

👍

Try to implement a logic that calculates the HMAC out of the up-mentioned sample callback, and if you got the same results, add this logic to your Transaction processed/response callback endpoints.