Pre-Payment Webhook

The pre-payment hook functionality sends a request to an endpoint of your choosing right before Foxy would otherwise submit the payment to the payment gateway, and allows for stores to apply custom validations for transactions prior to the request being set to the chosen payment method. A payload containing information about the current transaction is sent to the endpoint configured for this functionality, allowing the endpoint to respond to either approve or reject the transaction. If approved, the transaction will be sent on to the gateway to process, or if rejected the customer will be sent back to the checkout with a custom error message.

Common use cases for the pre-payment hook include:

  • Final inventory checks for low-stock or one-of-a-kind products.
  • Custom validations to ensure that certain product configurations are present.
  • Integrations with custom validation or fraud-check services (such as FraudLabsPro).

Enabling the pre-payment Hook

To enable the pre-payment hook, head to the “payment” setting page in your store's FoxyCart administration, ensuring that you've selected the payment set you're wanting to enable the hook for. Within the “Anti-Fraud Integrations” section, check the checkbox to “enable custom pre-payment hook” and complete the settings below.

pre-payment hook url
The URL for the endpoint script, which needs to be HTTPS, which will receive the JSON payload.
failure handling
If your endpoint script fails to load or respond correctly, use this setting to set whether the transaction should be automatically approved or rejected. If set to reject, a generic error message will be returned, which you can edit the error from the “language” section of your store's administration - look for the “error precheckout hook” language string within the “checkout” group.

Handling the request

Headers

Any requests made to your webhook's endpoint will also contain several special headers:

Header Description
Foxy-Webhook-Event Name of the event that triggered this payload. Currently validation/payment or validation/3ds.
Foxy-Store-ID The ID of the store that this webhook was triggered for.
Foxy-Store-Domain The current Foxy store domain that this webhook was triggered for.

Events

The prepayment webhook is primarily triggered right before the payment is sent to your gateway for handling. For this trigger, the Foxy-Webhook-Event header will be validation/payment.

For a couple gateways though, we do also trigger the prepayment webhook prior to the 3DSv2 challenge occurring, which has an event header of validation/3ds. These triggers exist for specific purposes, and are documented on the respective gateway wiki pages:

Example Payload

When the customer attempts to complete their purchase, after the Google reCAPTCHA is validated (if active), a POST request is sent off to your custom post-checkout hook endpoint with a JSON payload representing the current cart. It follows the same structure as our Hypermedia API, as the majority of the data comes from there.

The following is an example of the JSON payload, showing two products (with one having live shipping rates) and having a coupon added.

{
    "_links": {
    },
    "_embedded": {
        "fx:items": [
            {
                "_links": {
                },
                "_embedded": {
                    "fx:item_options": [
                        {
                            "_links": {
                            },
                            "name": "size",
                            "value": "medium",
                            "price_mod": 0,
                            "weight_mod": 0,
                            "date_created": null,
                            "date_modified": null
                        },
                        {
                            "_links": {
                            },
                            "name": "color",
                            "value": "red",
                            "price_mod": 0,
                            "weight_mod": 0,
                            "date_created": null,
                            "date_modified": null
                        }
                    ],
                    "fx:item_category": {
                        "_links": {
                        },
                        "admin_email_template_uri": "",
                        "customer_email_template_uri": "",
                        "code": "DEFAULT",
                        "name": "Default for all products",
                        "item_delivery_type": "flat_rate",
                        "max_downloads_per_customer": 3,
                        "max_downloads_time_period": 24,
                        "default_weight": 0,
                        "default_weight_unit": "LBS",
                        "default_length_unit": "IN",
                        "shipping_flat_rate_type": "per_item",
                        "shipping_flat_rate": 5,
                        "handling_fee_type": "none",
                        "handling_fee": 0,
                        "handling_fee_minimum": 0,
                        "handling_fee_percentage": 0,
                        "customs_value": 0,
                        "discount_type": "",
                        "discount_name": "",
                        "discount_details": "",
                        "send_customer_email": false,
                        "send_admin_email": false,
                        "admin_email": "",
                        "date_created": null,
                        "date_modified": null
                    }
                },
                "item_category_uri": "https://api.foxycart.com/item_categories/100",
                "name": "Example Product",
                "price": 15.99,
                "quantity": 1,
                "quantity_min": 0,
                "quantity_max": 0,
                "weight": 0,
                "code": "abc123",
                "parent_code": "",
                "discount_name": "",
                "discount_type": "",
                "discount_details": "",
                "subscription_frequency": "",
                "subscription_start_date": null,
                "subscription_next_transaction_date": null,
                "subscription_end_date": null,
                "is_future_line_item": false,
                "shipto": "Me",
                "url": "",
                "image": "",
                "length": 0,
                "width": 0,
                "height": 0,
                "expires": 0,
                "date_created": null,
                "date_modified": "2017-07-20T04:13:08-0700"
            },
            {
                "_links": {
                },
                "_embedded": {
                    "fx:item_category": {
                        "_links": {
                        },
                        "admin_email_template_uri": "",
                        "customer_email_template_uri": "",
                        "code": "live",
                        "name": "Live Rates",
                        "item_delivery_type": "shipped",
                        "max_downloads_per_customer": 3,
                        "max_downloads_time_period": 24,
                        "default_weight": 5,
                        "default_weight_unit": "LBS",
                        "default_length_unit": "IN",
                        "shipping_flat_rate_type": "per_order",
                        "shipping_flat_rate": 1,
                        "handling_fee_type": "none",
                        "handling_fee": 0,
                        "handling_fee_minimum": 0,
                        "handling_fee_percentage": 0,
                        "customs_value": 0,
                        "discount_type": "",
                        "discount_name": "",
                        "discount_details": "",
                        "send_customer_email": false,
                        "send_admin_email": false,
                        "admin_email": "",
                        "date_created": null,
                        "date_modified": null
                    }
                },
                "item_category_uri": "https://api.foxycart.com/item_categories/101",
                "name": "Another Product",
                "price": 20,
                "quantity": 1,
                "quantity_min": 0,
                "quantity_max": 0,
                "weight": 5,
                "code": "foo321",
                "parent_code": "",
                "discount_name": "",
                "discount_type": "",
                "discount_details": "",
                "subscription_frequency": "",
                "subscription_start_date": null,
                "subscription_next_transaction_date": null,
                "subscription_end_date": null,
                "is_future_line_item": false,
                "shipto": "Me",
                "url": "",
                "image": "",
                "length": 0,
                "width": 0,
                "height": 0,
                "expires": 0,
                "date_created": null,
                "date_modified": "2017-07-20T04:14:03-0700"
            }
        ],
        "fx:discounts": [
            {
                "code": "coupon",
                "amount": -3.6,
                "name": "Default Discount",
                "display": "-$3.60",
                "is_taxable": false,
                "is_future_discount": false
            }
        ],
        "fx:custom_fields": [
            {
                "name": "custom_note",
                "value": "Happy Birthday!",
                "is_hidden": 0
            }],
        "fx:shipment": {
            "address_name": "",
            "first_name": "John",
            "last_name": "Smith",
            "company": "",
            "address1": "Main Street",
            "address2": "",
            "city": "Saint Paul",
            "region": "MN",
            "postal_code": "55116",
            "country": "US",
            "origin_region": "TX",
            "origin_postal_code": "77018",
            "origin_country": "US",
            "shipping_service_id": 0,
            "shipping_service_description": "",
            "is_residential": false,
            "item_count": 2,
            "total_weight": 5,
            "total_customs_value": 0,
            "total_handling_fee": 0,
            "total_flat_rate_shipping": 5,
            "total_item_price": 35.99,
            "total_tax": 3.24,
            "total_shipping": 0,
            "total_price": 39.23
        },
        "fx:customer": {
            "id": "1512345",
            "first_name": "John",
            "last_name": "Smith",
            "email": "john@example.com",
            "tax_id": "",
            "is_anonymous": "0",
            "_embedded": {
                "fx:payments": [
                    {
                        "cc_type": "plastic",
                        "cc_number_masked": "xxxx xxxx xxxx 4242",
                        "cc_exp_month": "10",
                        "cc_exp_year": "2020",
                        "purchase_order": null
                    }
                ],
                "fx:default_billing_address": {
                    "country": "US",
                    "region": "MN",
                    "city": "Saint Paul",
                    "postal_code": "55116",
                    "address1": "Main Street",
                    "address2": "",
                    "company": "",
                    "full_name": "John Smith",
                    "first_name": "John",
                    "last_name": "Smith",
                    "phone": ""
                }
            }
        }
    },
    "customer_uri": "",
    "template_set_uri": "",
    "language": "",
    "locale_code": "en_US",
    "customer_ip": "192.168.0.1",
    "ip_country": "United States",
    "session_name": "fcsid",
    "session_id": "hvcv28l8md0qc8qt5rrjh4qo85",
    "total_item_price": 35.99,
    "total_tax": 3.24,
    "total_shipping": 14.23,
    "total_future_shipping": 0,
    "total_order": 49.86,
    "date_created": null,
    "date_modified": "2017-07-20T04:12:25-0700"
}

Notes

  • The payload includes several _links arrays. These contain helpful URI's that could be used through the Hypermedia API if you're also making use of that. If not, these can be safely ignored.

FoxyCart expects a response from your endpoint within 20 seconds of the request being sent - so you will need to ensure that any logic that your endpoint undertakes is completed quickly. Remember that as this request happens after the customer clicks to complete their transaction, any extended delays with your endpoint will mean they will also be waiting longer for a response to their transaction.

Sending a response

In response, FoxyCart expects a JSON payload in the following format to be output on the page (prettified for display purposes):

Approve

{
  "ok": true,
  "details": ""
}

Note that if you're using the Square Platform payment splitting integration, an additional parameter of payment_logic can be passed through as well.

Reject

{
  "ok": false,
  "details": "Sorry, we're all out of Example Products. Please remove that item from your cart and try again."
}

Only a valid JSON object should be output from your custom endpoint. If you output other elements to the page that is not part of a valid JSON object, the hook will fail to process and the default handling will occur.

Example Endpoint

The following is an example PHP endpoint that could be used to handle the pre-payment hook. Note that this is for illustrative purposes only. It'll work as is, but blocking based on IP or email isn't necessarily a recommended approach:

<?php
$rawPost = file_get_contents('php://input');
$cart_details = json_decode($rawPost, true);
 
$response = array(
    'ok' => true,
    'details' => "Sorry, we couldn't process your transaction. Please contact us to proceed."
);
 
$log_file = './log.txt';
$date = new DateTime();
$date_string = $date->format('Y-m-d H:i:s');
 
$log_line = $date_string . ': ' . $cart_details['customer_ip'] . ' - '. $cart_details['_embedded']['fx:customer']['email'] . ' -- ';
 
// Example: Loop through cart items and reject on specific product names
foreach($cart_details['_embedded']['fx:items'] as $item) {
    if ($item['name'] == 'Example Product') {
        $response['ok'] = false;
        $response['details'] = "Sorry, we're all out of Example Products. Please remove that item from your cart and try again.";
    }
}
 
// Example: Reject on specific IP addresses
// The IPs to block should be in a separate file named `ips_to_reject.txt`
// Note that this looks for exact matches.
// If you wanted to check CIDR ranges, try adding something like https://github.com/tholu/php-cidr-match
$ips_to_reject = file('./ips_to_reject.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($ips_to_reject) {
  foreach ($ips_to_reject as $ip) {
    if ($ip == $cart_details['customer_ip']) {
      $log_line .= "IP MATCH: $ip";
      $response['ok'] = false;
      break;
    }
  }
}
 
// Example: Reject on specific email addresses (exact matches)
// The emails to block should be in a separate file named `emails_to_reject.txt`
$emails_to_reject = file('./emails_to_reject.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($emails_to_reject) {
  foreach ($emails_to_reject as $email) {
    if (trim($email) == trim($cart_details['_embedded']['fx:customer']['email'])) {
      $log_line .= "EMAIL MATCH. REJECTING.";
      $response['ok'] = false;
      break;
    }
  }
}
 
$log_line .= (string) $response['ok'] . "\n";
$fp = fopen($log_file, 'a');
fwrite($fp, $log_line);
 
header('Content-Type: application/json');
print json_encode($response);

Debugging Errors

If your pre-payment hook endpoint fails to return a response, or returns a non-JSON response, a error will be added to your store's error log in the administration.

Site Tools