Documentation You are here: start » v » 2.0 » webhooks

Webhooks

Overview

Foxy's Webhook functionality allows you to create integrations which subscribe to notifications of events from your Foxy store, such as new transactions, renewals or changes to subscriptions, or changes to customers. When one of these events occurs, we send a HTTP POST payload to each of your configured endpoints with information about event's resource. Your endpoint can then perform any custom tasks you need, such as changing access permissions for customers, updating inventory levels, passing information on to fulfilment services, subscribing customers to mailing lists.

We currently have three different types of Webhook endpoints you can utilise, depending on your needs:

Sending Webhooks & Automatic Retries

When webhooks are triggered for one of your subscribed events, Foxy will wait for 1 minute for a response from your webhook endpoint. If a webhook fails to respond successfully (that is, if we don't receive a HTTP response code of 200) in that time for any reason, Foxy will automatically reattempt to connect to your endpoint 11 more times within the following hour after it was initially attempted. The retries will get progressively further apart over the hour: ranging from around 1 minute apart up to around 10 minutes apart. If any of the retries are successful, no further attempts will be made.

Transaction Statuses

For working with transactions with the webhook, a status node is included, which identifies what state the transaction is in. This can be important if you are only wanting to perform actions if a transaction has a specific status. Review the table below for details of the different statuses that we currently support:

Status Description
empty Transaction is complete
approved Transaction is complete
authorized Transaction is complete, but payment is only authorized
capturing (beta). The transaction payment is being captured, check again for final status
captured (beta). Transaction is complete, and the payment (that was previously authorized or verified) has been captured
declined The transaction has been declined by the gateway
pending The transaction has been completed by the customer, but a final status on the payment has not been received from the gateway. This status will be updated once a final response is received
rejected The transaction has been rejected by the gateway
voided (beta). The transaction has been voided by the store
refunding (beta). The transaction is being refunded, check again for final status
refunded (beta). The transaction has been refunded
verified The transaction has been completed by the customer, and the payment information has been verified, but no payment has actually been taken
problem There was a problem with this transaction, check the transaction report in the administration for details

Those statuses that are marked as “(beta)” are part of our beta advanced transaction editing functionality. These statuses are only used if your store is enabled for this functionality.

The verified status is only for those gateways that support verifying payment details, if configurable in your payment settings. This could happen if a gateway is set to only verify, or if it was a $0 transaction (like a future subscription) and the payment details were only verified as valid.

Some gateways will trigger multiple web hooks to be sent for a single transaction, as the status field is changed from pending to a final status - check the next section for details on that.

PayPal, Amazon Pay, and other Hosted Gateways

Using a hosted gateway? Multiple notifications may be sent per transaction.

If you use any of the hosted gateways (usually where a customer is redirected to a payment page hosted by the gateway itself), note that every transaction may generate more than one notification.

Every time our IPN (Instant Payment Notification) server gets a notification from the gateway we also send you a notification. Generally, these notifications should arrive to your webhook endpoint in order, but it's possible that these webhooks are sent to your endpoint within a very short window (less than 0.5 seconds), and with possible network delays, your endpoint may receive the notifications out of order. Make sure you account for the status value in your logic. For example, you likely don't want to ship products until a transaction status has gone from pending to approved.

The transaction webhook will include a status field which is updated with each notification, which you can see details on above. (This node may also be empty if the payment was made by a payment method for which Foxy doesn't currently support statuses.)

Most hosted gateways usually send 2 notifications: when the transaction is initiated with a pending status, and when it is complete with it's final status (of either approved, declined or rejected). There are exceptions to this though, so be sure to test your set up with your chosen gateways and only process transactions that are in your required state.

Here is the current list of hosted gateways which take advantage of the status field:

  • 2Checkout
  • Adyen
  • Amazon
  • BitPay
  • Coinbase
  • Comgate
  • Cybersource Secure Acceptance Web/Mobile
  • DIBS
  • Dwolla
  • MercadoPago
  • Mollie
  • Ogone
  • PayPal Commerce Platform
  • PayPal Express Checkout
  • PayPal Plus
  • PesaPal
  • Skrill

Handling Failed Webhooks

If after all retries there is at least one webhook that was unsuccessful, an entry will be added to the stores error log with details about each webhook that didn't succeed. The error information will be made up of the following parts:

TITLE (TYPE): ERROR_MESSAGE [HTTP_CODE]
TITLE
The title of the webhook
TYPE
The type of webhook - json, zapier, webflow.
ERROR_MESSAGE
The error message received from the endpoint, trimmed to 500 characters. For the JSON Webhook this is the body of the response.
HTTP_CODE
The HTTP status code of the response

Along with the error log, an email will also be sent to the store's configured email address(es) with some general information about the error.

Did the event complete?

The event that triggered the webhook still completed successfully. If it was a transaction webhook, the customer was charged and you'll receive payment for the transaction as your chosen payment gateway processes it.

Troubleshooting Webhook Errors

If at least one of your webhooks failed to handle the payload successfully - you will want to correct the error and refeed the payload to the failed endpoints.

Check the error: You can see the specific error that was triggered by your webhook by viewing the errors page of your stores FoxyCart administration. Check out this page: http://wiki.foxycart.com/primer/errors for information on what the error logs mean for your webhook. Based on the particular error you're receiving, you may need to make changes to your webhook to correct the issue. If you don't manage the specific webhook that has errored, you'll need to get in touch with the people that do with details of the error.

Retrying Webhooks

If you want to trigger a transaction to be resent to your webhook, you can do that from your store's FoxyCart administration by following these steps:

  1. Load the transactions report section of the administration.
  2. Find the transaction that you want to retry - if it has previously failed to send to your webhooks successfully, it will be highlighted in red - and expand that transaction by clicking the “[+]” link.
  3. At the bottom of the transaction's details, look for the “Webhooks” section.
  4. Check the checkbox next to any webhooks that you want to send, and press the “Refeed selected webhooks” button.

After triggering the transaction to be sent to your webhook again, the system will re-attempt the process again for another hour as detailed above - or until it is successful.

JSON Webhook

The JSON webhook is, as you may have guessed, a JSON formatted payload sent to your endpoint.

The below documentation is for our latest version of our webhooks functionality (currently labelled as “Webhooks Next” in the admin). For information related to our previous webhooks version, see the Webhooks Legacy section below.

Creating a webhook

JSON webhooks can be created from the “Integrations” section of the FoxyCart administration. Enable the “JSON Webhooks” checkbox within the “webhooks next” section, and you'll be able to specify a new webhook endpoint.

Each webhook requires a name, a URL and an encryption key (which will be automatically set with a value). Enter an identifying name and the URL to the endpoint that will be receiving the webhook payload. We'll explain how to create that endpoint in the next section.

If you'd like to provide your own encryption key for the webhook, click the “Show secret” button and enter in your own secure key there.

Subscribed resources

There are several different resource types that you can subscribe to for the webhooks, one resource per webhook. The following resource types are currently supported:

Transactions

Triggered whenever a transaction is completed, it's payment status is updated, or it is captured/refunded/voided (using our beta transaction functionality)

Events: transaction/created, transaction/captured, transaction/refunded, transaction/voided

Subscriptions

Triggered whenever a subscription resource is created or updated. Subscriptions are created when first purchased as part of a transaction, and an update could include a change to it's next or end date, or the past due amount. Currently not triggered based on creation/update requests from the API (but coming soon).

Events: subscription/created, subscription/modified

Customers

Triggered whenever a customer resource is created or updated. Currently not triggered based on creation/update requests from the API (but coming soon).

Events: customer/created, customer/modified

Note: Once you have created a webhook, it's not supported to change the subscribed resource type. If you need to change that for a given resource, you can delete the existing webhook and create another.

API filter query string

The JSON webhooks uses our API as the source of data, and by default will just return the base resource as the payload (for example, the transaction webhook will just send the transaction resource which won't include any details on items or addresses). You can specify a query string value though within the API filter query string input to adjust what data is sent through, and we provide default query strings for each resource when creating the webhook in the admin.

The following are examples of query strings that you might want to use:

Transactions

  • Provide all data related to the transaction (default)
    zoom=applied_taxes,billing_addresses,custom_fields,customer,discounts,items,items:item_category,items:item_options,payments,shipments
  • Just send the transaction ID and status
    fields=id,status

Subscriptions

  • Provide all data related to the subscription (default)
    zoom=customer,customer:default_billing_address,customer:default_shipping_address,transaction_template,transaction_template:discounts,transaction_template:items,transaction_template:items:item_category,transaction_template:items:item_options,transaction_template:applied_coupon_codes,transaction_template:custom_fields

Customers

  • Provide all data related to the customer (default)
    zoom=default_billing_address,default_shipping_address,default_payment_method,customer_addresses

Saving The Webhook

To save changes to the webhook, click the “Update Webhooks Next” button below the webhooks.

Upon saving, a GET request is sent to your endpoint to confirm that the URL is valid. You'll need to ensure that your endpoint is able to successfully accept GET requests for this to complete and allow you to save the webhook.

Testing Webhooks: Helpful Tips

It's often helpful to see exactly what the webhook request (sent to your servers) looks like. Though we don't offer this functionality natively, there are a variety of free services that you can use to send and view webhook requests. One we like is Webhook.site. You can get an endpoint there, configure Foxy to send webhooks to that URI, then review the full request (body and headers). There are others like Hookbin, or Requestb.in (which you can set up to self-host if desired, like webhook.site).

Receiving a webhook

Once you've configured your webhook - whenever actions happen that relate to the events you're subscribed to, a JSON payload containing information about that event will be sent to your endpoint. If your endpoint is secured with HTTPS, then the data is sent unencrypted ready for you to use. If your endpoint is not secured though, the data will be encrypted using AES-256-CBC before being sent to your endpoint, meaning you will need to then decrypt it before you can use it.

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. Review the resources above for the supported events per resource.
Foxy-Webhook-Signature A HMAC SHA256 signature of the payload, using the webhook's encryption key. Used for verifying the contents of the payload.
Foxy-Webhook-Refeed A boolean to signify if this payload has been refed. If false, then this is the first time this specific instance of the event has been triggered.
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.

Example Payload

The JSON payload will follow the same structure as our Hypermedia API. Most of the objects will include the API's _links array. These contain helpful URI's that could be used to access the specific resources in the API if you're also making use of that. If not, these can be safely ignored.

The source of the payload data is our Hypermedia API, so you can use the documentation for the different resources for further information on the data it contains.

Webhook payloads will always reflect the most current state of the resource at the time it was sent. If a webhook is resent, it will be the current state of the resource, rather than the state of the resource at the time that it was sent.

In the example payloads below - to save space, the _links arrays have been cleared out. It also assumes the default example API filter query string has been used (as described above).

Transaction

{
    "_links": { },
    "_embedded": {
        "fx:customer": {
            "_links": { },
            "_embedded": {
                "fx:attributes": [
                    {
                        "_links": { },
                        "name": "Loyalty Points",
                        "value": "100",
                        "visibility": "private",
                        "date_created": "2020-09-18T08:39:22-0700",
                        "date_modified": "2020-09-18T08:39:22-0700"
                    },
                    {
                        "_links": { },
                        "name": "Level",
                        "value": "Bronze",
                        "visibility": "public",
                        "date_created": "2020-09-18T08:40:53-0700",
                        "date_modified": "2020-09-18T08:40:53-0700"
                    },
                    {
                        "_links": { },
                        "name": "Loyalty_Level",
                        "value": "Blue",
                        "visibility": "public",
                        "date_created": "2021-05-17T03:43:36-0700",
                        "date_modified": "2021-05-17T03:43:36-0700"
                    }
                ]
            },
            "id": 12345678,
            "last_login_date": "2022-07-29T04:10:03-0700",
            "first_name": "John",
            "last_name": "Person",
            "email": "john@example.com",
            "tax_id": "",
            "password_salt": "",
            "password_hash": "$P$Bfo4kbFOqal4gqR9RcE/xg2lK75zK1",
            "password_hash_type": "phpass",
            "password_hash_config": "8",
            "forgot_password": "",
            "forgot_password_timestamp": null,
            "is_anonymous": false,
            "date_created": null,
            "date_modified": "2022-08-01T08:58:15-0700"
        },
        "fx:payments": [
            {
                "_links": { },
                "type": "plastic",
                "gateway_type": "authorize",
                "processor_response": "Authorize.net Transaction ID:123456789",
                "processor_response_details": "a:1:{s:20:\"transactionReference\";s:11:\"123456789\";}",
                "purchase_order": "",
                "cc_number_masked": "xxxxxxxxx4242",
                "cc_type": "Visa",
                "cc_exp_month": "01",
                "cc_exp_year": "2023",
                "fraud_protection_score": 0,
                "paypal_payer_id": "",
                "third_party_id": "",
                "amount": 29.99,
                "date_created": "2022-08-01T08:58:16-0700",
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ],
        "fx:items": [
            {
                "_links": { },
                "_embedded": {
                    "fx:item_options": [
                        {
                            "_links": { },
                            "name": "size",
                            "value": "Large",
                            "price_mod": 0,
                            "weight_mod": 0,
                            "date_created": null,
                            "date_modified": null
                        },
                        {
                            "_links": { },
                            "name": "color",
                            "value": "Surprise Me",
                            "price_mod": 0,
                            "weight_mod": 0,
                            "date_created": null,
                            "date_modified": null
                        }
                    ],
                    "fx:item_category": {
                        "_links": { },
                        "admin_email_template_uri": "",
                        "customer_email_template_uri": "",
                        "gift_recipient_email_template_uri": "https://api.foxycart.com/email_templates/12345",
                        "code": "DEFAULT",
                        "name": "Default for all products",
                        "item_delivery_type": "flat_rate",
                        "max_downloads_per_customer": 3,
                        "max_downloads_time_period": 24,
                        "default_weight": 1,
                        "default_weight_unit": "LBS",
                        "default_length_unit": "IN",
                        "shipping_flat_rate_type": "per_order",
                        "shipping_flat_rate": 5,
                        "handling_fee_type": "flat_percent_with_minimum",
                        "handling_fee": 1,
                        "handling_fee_minimum": 0,
                        "handling_fee_percentage": 10,
                        "customs_value": 0,
                        "discount_type": "",
                        "discount_name": "Gift Set",
                        "discount_details": "single|3-9.98|5-19.96",
                        "send_customer_email": false,
                        "send_admin_email": false,
                        "admin_email": "",
                        "date_created": null,
                        "date_modified": "2022-07-28T01:44:56-0700"
                    }
                },
                "item_category_uri": "https://api.foxycart.com/item_categories/12345",
                "name": "My Subscription",
                "price": 24.99,
                "quantity": 1,
                "quantity_min": 0,
                "quantity_max": 0,
                "weight": 1,
                "code": "mysub",
                "parent_code": "",
                "discount_name": "",
                "discount_type": "",
                "discount_details": "",
                "subscription_frequency": "1m",
                "subscription_start_date": "2022-08-01T00:00:00-0700",
                "subscription_next_transaction_date": "2022-09-01T00:00:00-0700",
                "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": "2022-08-01T08:57:13-0700"
            }
        ],
        "fx:applied_taxes": [
            {
                "_links": { },
                "rate": 0,
                "name": "Endpoint",
                "amount": 0,
                "apply_to_handling": true,
                "apply_to_shipping": true,
                "is_future_tax": false,
                "shipto": "",
                "date_created": "2022-08-01T08:58:16-0700",
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ],
        "fx:custom_fields": [
            {
                "_links": { },
                "name": "newsletter_subscribe",
                "value": "1",
                "is_hidden": false,
                "date_created": "2022-08-01T08:58:16-0700",
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ],
        "fx:discounts": [
            {
                "_links": { },
                "code": "mycode",
                "amount": -2.5,
                "name": "My Coupon",
                "display": "-$2.50",
                "is_taxable": false,
                "is_future_discount": false,
                "date_created": "2022-08-01T08:58:16-0700",
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ],
        "fx:shipments": [
            {
                "_links": { },
                "address_name": "Me",
                "first_name": "John",
                "last_name": "Person",
                "company": "",
                "address1": "123 Testing Street",
                "address2": "",
                "city": "LOS ANGELES",
                "region": "CA",
                "postal_code": "90028",
                "country": "US",
                "phone": "",
                "shipping_service_id": 0,
                "shipping_service_description": "Custom Flat Rate Shipping",
                "total_item_price": 24.99,
                "total_tax": 0,
                "total_shipping": 7.5,
                "total_price": 29.99,
                "date_created": "2022-08-01T08:58:16-0700",
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ],
        "fx:billing_addresses": [
            {
                "_links": { },
                "address_name": "Default Billing Address",
                "first_name": "John",
                "last_name": "Person",
                "company": "",
                "address1": "123 Testing Street",
                "address2": "",
                "city": "LOS ANGELES",
                "region": "CA",
                "customer_postal_code": "90028",
                "customer_country": "US",
                "customer_phone": "",
                "date_created": null,
                "date_modified": "2022-08-01T08:58:16-0700"
            }
        ]
    },
    "id": 1976574034,
    "display_id": "foo00943",
    "is_test": true,
    "hide_transaction": false,
    "data_is_fed": false,
    "type": "transaction",
    "source": "cit_ecommerce",
    "transaction_date": "2022-08-02T01:28:16+0930",
    "locale_code": "en_US",
    "customer_first_name": "John",
    "customer_last_name": "Person",
    "customer_tax_id": "",
    "customer_email": "john@example.com",
    "customer_ip": "11.22.123.456",
    "ip_country": "United States",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15",
    "total_item_price": 24.99,
    "total_tax": 0,
    "total_shipping": 7.5,
    "total_future_shipping": 0,
    "total_order": 29.99,
    "status": "captured",
    "date_created": null,
    "date_modified": "2022-08-01T08:58:16-0700",
    "currency_code": "USD",
    "currency_symbol": "$"
}

Within the payload, the following attributes can be helpful for handling the payload:

type
What type of transaction this was, could be transaction, updateinfo, subscription_modification, subscription_renewal or subscription_cancellation

Subscription

{
    "_links": {
        // Note: These two links are included in order to illustrate where you can obtain subscription-related URLs. There are many other links included in the webhook, and you can explore them in our API documentation at https://api.foxycart.com.
        "fx:sub_token_url": {
            "href": "https://your-store.foxycart.com/cart?sub_token=3599338e72595b521d62b3ca7e286c212346ec3fc6f859ef139e5d9796900fad",
            "title": "This Sub Token",
            "type": "text/html"
        },
        "fx:sub_modification_url": {
            "href": "https://your-store.foxycart.com/cart?sub_token=3599338e72595b521d62b3ca7e286c212346ec3fc6f859ef139e5d9796900fad&redirect=https://your-store.com",
            "title": "URL to modify this subscription",
            "type": "text/html"
        }
     },
    "_embedded": {
        "fx:customer": {
            "_links": { },
            "_embedded": {
                "fx:attributes": [
                    {
                        "_links": { },
                        "name": "Loyalty Points",
                        "value": "100",
                        "visibility": "private",
                        "date_created": "2020-09-18T08:39:22-0700",
                        "date_modified": "2020-09-18T08:39:22-0700"
                    },
                    {
                        "_links": { },
                        "name": "Level",
                        "value": "Bronze",
                        "visibility": "public",
                        "date_created": "2020-09-18T08:40:53-0700",
                        "date_modified": "2020-09-18T08:40:53-0700"
                    },
                    {
                        "_links": { },
                        "name": "Loyalty_Level",
                        "value": "Blue",
                        "visibility": "public",
                        "date_created": "2021-05-17T03:43:36-0700",
                        "date_modified": "2021-05-17T03:43:36-0700"
                    }
                ],
                "fx:default_billing_address": {
                    "_links": { },
                    "address_name": "Default Billing Address",
                    "first_name": "John",
                    "last_name": "Person",
                    "company": "",
                    "address1": "123 Testing Street",
                    "address2": "",
                    "city": "LOS ANGELES",
                    "region": "CA",
                    "postal_code": "90028",
                    "country": "US",
                    "phone": "",
                    "is_default_billing": true,
                    "is_default_shipping": false,
                    "date_created": null,
                    "date_modified": "2022-08-01T08:58:15-0700"
                },
                "fx:default_shipping_address": {
                    "_links": { },
                    "address_name": "Me",
                    "first_name": "John",
                    "last_name": "Person",
                    "company": "",
                    "address1": "123 Testing Street",
                    "address2": "",
                    "city": "LOS ANGELES",
                    "region": "CA",
                    "postal_code": "90028",
                    "country": "US",
                    "phone": "",
                    "is_default_billing": false,
                    "is_default_shipping": true,
                    "date_created": null,
                    "date_modified": "2022-08-01T08:58:15-0700"
                }
            },
            "id": 12345678,
            "last_login_date": "2022-07-29T04:10:03-0700",
            "first_name": "John",
            "last_name": "Person",
            "email": "john@example.com",
            "tax_id": "",
            "password_salt": "",
            "password_hash": "$P$Bfo4kbFOqal4gqR9RcE/xg2lK75zK1",
            "password_hash_type": "phpass",
            "password_hash_config": "8",
            "forgot_password": "",
            "forgot_password_timestamp": null,
            "is_anonymous": false,
            "date_created": null,
            "date_modified": "2022-08-01T08:58:15-0700"
        },
        "fx:transaction_template": {
            "_links": { },
            "_embedded": {
                "fx:items": [
                    {
                        "_links": { },
                        "_embedded": {
                            "fx:item_options": [
                                {
                                    "_links": { },
                                    "name": "size",
                                    "value": "Large",
                                    "price_mod": 0,
                                    "weight_mod": 0,
                                    "date_created": null,
                                    "date_modified": null
                                },
                                {
                                    "_links": { },
                                    "name": "color",
                                    "value": "Surprise Me",
                                    "price_mod": 0,
                                    "weight_mod": 0,
                                    "date_created": null,
                                    "date_modified": null
                                }
                            ],
                            "fx:item_category": {
                                "_links": { },
                                "admin_email_template_uri": "",
                                "customer_email_template_uri": "",
                                "gift_recipient_email_template_uri": "https://api.foxycart.com/email_templates/1234",
                                "code": "DEFAULT",
                                "name": "Default for all products",
                                "item_delivery_type": "flat_rate",
                                "max_downloads_per_customer": 3,
                                "max_downloads_time_period": 24,
                                "default_weight": 1,
                                "default_weight_unit": "LBS",
                                "default_length_unit": "IN",
                                "shipping_flat_rate_type": "per_order",
                                "shipping_flat_rate": 5,
                                "handling_fee_type": "flat_percent_with_minimum",
                                "handling_fee": 1,
                                "handling_fee_minimum": 0,
                                "handling_fee_percentage": 10,
                                "customs_value": 0,
                                "discount_type": "",
                                "discount_name": "Gift Set",
                                "discount_details": "single|3-9.98|5-19.96",
                                "send_customer_email": false,
                                "send_admin_email": false,
                                "admin_email": "",
                                "date_created": null,
                                "date_modified": "2022-07-28T01:44:56-0700"
                            }
                        },
                        "item_category_uri": "https://api.foxycart.com/item_categories/1234",
                        "name": "My Subscription",
                        "price": 24.99,
                        "quantity": 1,
                        "quantity_min": 0,
                        "quantity_max": 0,
                        "weight": 1,
                        "code": "mysub",
                        "parent_code": "",
                        "discount_name": "",
                        "discount_type": "",
                        "discount_details": "",
                        "subscription_frequency": "1m",
                        "subscription_start_date": "2022-08-01T00:00:00-0700",
                        "subscription_next_transaction_date": "2022-09-01T00:00:00-0700",
                        "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": "2022-08-01T08:58:16-0700"
                    }
                ],
                "fx:custom_fields": [
                    {
                        "_links": { },
                        "name": "newsletter_subscribe",
                        "value": "1",
                        "is_hidden": false,
                        "date_created": "2022-08-01T08:58:16-0700",
                        "date_modified": "2022-08-01T08:58:16-0700"
                    }
                ],
                "fx:discounts": [
                    {
                        "_links": { },
                        "code": "mycode",
                        "amount": -2.5,
                        "name": "My Coupon",
                        "display": "-$2.50",
                        "is_taxable": false,
                        "is_future_discount": false,
                        "date_created": "2022-08-01T08:58:16-0700",
                        "date_modified": "2022-08-01T08:58:16-0700"
                    }
                ]
            },
            "customer_uri": "https://api.foxycart.com/customers/12345678",
            "template_set_uri": "https://api.foxycart.com/template_sets/1234",
            "payment_method_uri": "",
            "language": "",
            "locale_code": "en_US",
            "use_customer_shipping_address": false,
            "billing_first_name": "John",
            "billing_last_name": "Person",
            "billing_company": "",
            "billing_address1": "123 Testing Street",
            "billing_address2": "",
            "billing_city": "LOS ANGELES",
            "billing_state": "CA",
            "billing_postal_code": "90028",
            "billing_country": "US",
            "billing_phone": "",
            "customer_email": "john@example.com",
            "shipping_first_name": "John",
            "shipping_last_name": "Person",
            "shipping_company": "",
            "shipping_address1": "123 Testing Street",
            "shipping_address2": "",
            "shipping_city": "LOS ANGELES",
            "shipping_state": "CA",
            "shipping_postal_code": "90028",
            "shipping_country": "US",
            "shipping_phone": "",
            "total_item_price": 24.99,
            "total_tax": 0,
            "total_shipping": 7.5,
            "total_future_shipping": 0,
            "total_order": 29.99,
            "date_created": null,
            "date_modified": "2022-08-01T08:58:16-0700",
            "currency_code": "USD",
            "currency_symbol": "$"
        }
    },
    "start_date": "2022-08-01T00:00:00-0700",
    "next_transaction_date": "2022-09-01T00:00:00-0700",
    "end_date": null,
    "frequency": "1m",
    "error_message": "",
    "past_due_amount": 0,
    "first_failed_transaction_date": null,
    "is_active": true,
    "third_party_id": "",
    "cancellation_source": "",
    "date_created": "2022-08-01T08:58:16-0700",
    "date_modified": "2022-08-01T08:58:16-0700",
    "payment_type": "plastic"
}

Customer

{
    "_links": { },
    "_embedded": {
        "fx:attributes": [
            {
                "_links": { },
                "name": "Loyalty Points",
                "value": "100",
                "visibility": "private",
                "date_created": "2020-09-18T08:39:22-0700",
                "date_modified": "2020-09-18T08:39:22-0700"
            },
            {
                "_links": { },
                "name": "Level",
                "value": "Bronze",
                "visibility": "public",
                "date_created": "2020-09-18T08:40:53-0700",
                "date_modified": "2020-09-18T08:40:53-0700"
            },
            {
                "_links": { },
                "name": "Loyalty_Level",
                "value": "Blue",
                "visibility": "public",
                "date_created": "2021-05-17T03:43:36-0700",
                "date_modified": "2021-05-17T03:43:36-0700"
            }
        ],
        "fx:default_billing_address": {
            "_links": { },
            "address_name": "Default Billing Address",
            "first_name": "John",
            "last_name": "Person",
            "company": "",
            "address1": "123 Testing Street",
            "address2": "",
            "city": "LOS ANGELES",
            "region": "CA",
            "postal_code": "90028",
            "country": "US",
            "phone": "",
            "is_default_billing": true,
            "is_default_shipping": false,
            "date_created": null,
            "date_modified": "2022-08-01T08:58:15-0700"
        },
        "fx:default_shipping_address": {
            "_links": { },
            "address_name": "Me",
            "first_name": "John",
            "last_name": "Person",
            "company": "",
            "address1": "123 Testing Street",
            "address2": "",
            "city": "LOS ANGELES",
            "region": "CA",
            "postal_code": "90028",
            "country": "US",
            "phone": "",
            "is_default_billing": false,
            "is_default_shipping": true,
            "date_created": null,
            "date_modified": "2022-08-01T08:58:15-0700"
        },
        "fx:default_payment_method": {
            "_links": { },
            "save_cc": true,
            "cc_type": "Visa",
            "cc_number_masked": "xxxxxxxxx4242",
            "cc_exp_month": "01",
            "cc_exp_year": "2023",
            "date_created": null,
            "date_modified": "2022-08-01T08:58:15-0700"
        },
        "fx:customer_addresses": [
            {
                "_links": { },
                "address_name": "Work",
                "first_name": "John",
                "last_name": "Person",
                "company": "",
                "address1": "1024 Commercial Road",
                "address2": "",
                "city": "LOS ANGELES",
                "region": "CA",
                "postal_code": "90028",
                "country": "US",
                "phone": "",
                "is_default_billing": false,
                "is_default_shipping": false,
                "date_created": "2014-09-16T04:21:31-0700",
                "date_modified": "2014-10-06T20:00:17-0700"
            },
            {
                "_links": { },
                "address_name": "Mum",
                "first_name": "Jane",
                "last_name": "Person",
                "company": "",
                "address1": "88 High Street",
                "address2": "",
                "city": "NEW YORK",
                "region": "NY",
                "postal_code": "10001",
                "country": "US",
                "phone": "",
                "is_default_billing": false,
                "is_default_shipping": false,
                "date_created": "2014-10-14T19:12:17-0700",
                "date_modified": "2022-01-17T09:02:48-0800"
            }
        ]
    },
    "id": 12345678,
    "last_login_date": "2022-07-29T04:10:03-0700",
    "first_name": "John",
    "last_name": "Person",
    "email": "john@example.com",
    "tax_id": "",
    "password_salt": "",
    "password_hash": "$P$Bfo4kbFOqal4gqR9RcE/xg2lK75zK1",
    "password_hash_type": "phpass",
    "password_hash_config": "8",
    "forgot_password": "",
    "forgot_password_timestamp": null,
    "is_anonymous": false,
    "date_created": null,
    "date_modified": "2022-08-01T08:58:15-0700"
}

JSON Webhook (Legacy)

The following information is for our legacy webhook functionality. If you're creating a new webhook, you'll use the new version documented above.

Creating a webhook

JSON webhooks can be created from the “Integrations” section of the FoxyCart administration. Enable the “JSON Webhooks” checkbox, and you'll be able to specify a new webhook endpoint.

Each webhook requires a name, a URL and an encryption key (which will be automatically set with a value). Enter an identifying name and the URL to the endpoint that will be receiving the webhook payload. We'll explain how to create that endpoint in the next section.

If you'd like to provide your own encryption key for the webhook, click the “Show secret” button and enter in your own secure key there.

You can add multiple endpoints by clicking the “Add one more URL” button within the JSON webhooks section.

Once you've added in your new webhook - click the “Update Webhooks” button to save.

Webhook Events

JSON webhooks can be triggered for different events that occur in your store, selected when creating or editing the webhook. Currently we support events on transactions and subscriptions, with more to come in the future.

Transaction Created

The Transaction Created webhook option receives any transaction for the store, whether it's a new transaction, a subscription renewal, an update of customer info, or a customer-initiated subscription cancel (technically, when a subscription end date is set – always a future date). Those all generate a transaction in the system, so are fed to the endpoint.

Transaction Captured/Refunded/Voided

This currently relies on beta transaction editing functionality that is manually enabled on a per store basis.

The three events for Captured, Refunded and Voided occur when an existing transaction is either manually captured, refunded or voided. This can be triggered either through the transaction report section of the Foxy administration, or through the API. Note that the Captured event here is for a previously uncaptured transaction that was either verified or authorized that is then manually captured.

The original transaction (whether it was fully captured, or only authorized or verified) would have triggered the Transaction Created event at that time. The Captured, Refunded or Voided event is then in relation to the change of the payment status for the transaction.

Subscription Cancelled

The Subscription Cancelled event occurs when a subscription is actually ended, on the end date set on the subscription.

Subscriptions that are made inactive directly via the administration or the API do not trigger this event. Customers completing a sub_cancel checkout type also don't trigger this event, as the sub_cancel sets the subscriptions end date to either tomorrow or the next transaction date (depending on the store settings). A sub_cancel checkout will trigger a Transaction Created webhook instead, with a transaction_type attribute in the payload of subscription_cancellation, and the new subscription_end_date set on the subscription product.

Testing Webhooks: Helpful Tips

It's often helpful to see exactly what the webhook request (sent to your servers) looks like. Though we don't offer this functionality natively, there are a variety of free services that you can use to send and view webhook requests. One we like is Webhooks.site. You can get an endpoint there, configure Foxy to send webhooks to that URI, then review the full request (body and headers). There are others like Hookbin, or Requestb.in (which you can set up to self-host if desired, like webhook.site).

Receiving a webhook

Once you've configured your webhook - whenever actions happen that relate to the events you're subscribed to, a JSON payload containing information about that event will be sent to your endpoint. If your endpoint is secured with HTTPS, then the data is sent unencrypted ready for you to use. If your endpoint is not secured though, the data will be encrypted using AES-256-CBC before being sent to your endpoint, meaning you will need to then decrypt it before you can use it.

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 transaction/created, transaction/captured, transaction/refunded, transaction/voided, subscription/cancelled.
Foxy-Webhook-Signature A HMAC SHA256 signature of the payload, using the webhook's encryption key. Used for verifying the contents of the payload.
Foxy-Webhook-Refeed A boolean to signify if this payload has been refed. If false, then this is the first time this specific instance of the event has been triggered.
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.

Example Payload

The JSON payload will follow much the same structure as our Hypermedia API. Most of the objects will include the API's _links array. These contain helpful URI's that could be used to access the specific resources in the API if you're also making use of that. If not, these can be safely ignored.

The source of the payload data is our Hypermedia API, so you can use the documentation for the different resources for further information on the data it contains.

In the example payloads below - to save space, the _links arrays have been cleared out.

Transaction Created

{
    "_embedded": {
        "fx:applied_taxes": [
            {
                "_links": { },
                "amount": 2.24,
                "apply_to_handling": true,
                "apply_to_shipping": true,
                "date_created": null,
                "date_modified": null,
                "is_future_tax": false,
                "name": "Global Tax",
                "rate": 5,
                "shipto": null
            }
        ],
        "fx:billing_addresses": [
            {
                "_links": { },
                "address1": "123 Test Street",
                "address2": "Level 2",
                "address_name": "Default Billing Address",
                "city": "SAINT PAUL",
                "company": "Some Company",
                "customer_country": "US",
                "customer_phone": "123456789",
                "customer_postal_code": "55115",
                "date_created": null,
                "date_modified": "2018-03-01T00:00:00-0700",
                "first_name": "John",
                "last_name": "Smith",
                "region": "MN"
            }
        ],
        "fx:custom_fields": [
            {
                "_links": { },
                "date_created": null,
                "date_modified": null,
                "is_hidden": true,
                "name": "custom_attribute",
                "value": "reseller"
            },
            {
                "_links": { },
                "date_created": null,
                "date_modified": null,
                "is_hidden": false,
                "name": "newsletter_subscribe",
                "value": "1"
            }
        ],
        "fx:customer": {
            "_links": { },
            "date_created": null,
            "date_modified": null,
            "email": "john@example.com",
            "first_name": "John",
            "forgot_password": null,
            "forgot_password_timestamp": null,
            "id": 12345,
            "is_anonymous": false,
            "last_login_date": "2018-03-01T00:00:00-0700",
            "last_name": "Smith",
            "password_hash": "$2y$13$Jx7p31h18VnW.ab38QaYJu1QCzsyMQZKdY5s21nYewmGQj0OQCyIq",
            "password_hash_config": "13",
            "password_hash_type": "bcrypt",
            "password_salt": null,
            "tax_id": null
        },
        "fx:discounts": [
            {
                "_links": { },
                "amount": -3.79,
                "code": "my-code",
                "date_created": null,
                "date_modified": null,
                "display": "-$3.79",
                "is_future_discount": false,
                "is_taxable": false,
                "name": "Special Offer"
            }
        ],
        "fx:items": [
            {
                "_embedded": {
                    "fx:item_category": {
                        "_links": { },
                        "admin_email": null,
                        "admin_email_template_uri": null,
                        "code": "DEFAULT",
                        "customer_email_template_uri": null,
                        "customs_value": 0,
                        "date_created": null,
                        "date_modified": null,
                        "default_length_unit": "IN",
                        "default_weight": 0,
                        "default_weight_unit": "LBS",
                        "discount_details": "single|3-5|5-10",
                        "discount_name": "Bulk Order",
                        "discount_type": "quantity_percentage",
                        "handling_fee": 1,
                        "handling_fee_minimum": 0,
                        "handling_fee_percentage": 80,
                        "handling_fee_type": "none",
                        "item_delivery_type": "flat_rate",
                        "max_downloads_per_customer": 3,
                        "max_downloads_time_period": 24,
                        "name": "Default for all products",
                        "send_admin_email": false,
                        "send_customer_email": false,
                        "shipping_flat_rate": 2,
                        "shipping_flat_rate_type": "per_item"
                    },
                    "fx:item_options": [
                        {
                            "_links": { },
                            "date_created": null,
                            "date_modified": null,
                            "name": "size",
                            "price_mod": 0,
                            "value": "Large",
                            "weight_mod": 0
                        },
                        {
                            "_links": { },
                            "date_created": null,
                            "date_modified": null,
                            "name": "color",
                            "price_mod": 0,
                            "value": "Blue",
                            "weight_mod": 0
                        },
                        {
                            "_links": { },
                            "date_created": null,
                            "date_modified": null,
                            "name": "Bulk Order",
                            "price_mod": -0.11,
                            "value": "-2%",
                            "weight_mod": 0
                        }
                    ]
                },
                "_links": { },
                "base_price": 5.5,
                "code": "my-widget",
                "date_created": null,
                "date_modified": "2018-03-01T00:00:00-0700",
                "delivery_type": "flat_rate",
                "discount_details": null,
                "discount_name": null,
                "discount_type": null,
                "downloadable_url": null,
                "expires": 0,
                "height": 0,
                "image": null,
                "is_future_line_item": false,
                "item_category_uri": "https://api.foxycart.com/item_categories/100",
                "length": 0,
                "name": "My Widget",
                "parent_code": null,
                "price": 5.5,
                "quantity": 5,
                "quantity_max": 0,
                "quantity_min": 0,
                "shipto": null,
                "sub_token_url": null,
                "subscription_end_date": null,
                "subscription_frequency": null,
                "subscription_next_transaction_date": null,
                "subscription_start_date": null,
                "url": null,
                "weight": 0,
                "width": 0
            },
            {
                "_embedded": {
                    "fx:item_category": {
                        "_links": { },
                        "admin_email": null,
                        "admin_email_template_uri": null,
                        "code": "subs",
                        "customer_email_template_uri": null,
                        "customs_value": 0,
                        "date_created": null,
                        "date_modified": null,
                        "default_length_unit": "IN",
                        "default_weight": 1,
                        "default_weight_unit": "LBS",
                        "discount_details": "",
                        "discount_name": "",
                        "discount_type": null,
                        "handling_fee": 0,
                        "handling_fee_minimum": 0,
                        "handling_fee_percentage": 0,
                        "handling_fee_type": "none",
                        "item_delivery_type": "shipped",
                        "max_downloads_per_customer": 3,
                        "max_downloads_time_period": 24,
                        "name": "Subscriptions",
                        "send_admin_email": false,
                        "send_customer_email": false,
                        "shipping_flat_rate": 0,
                        "shipping_flat_rate_type": "per_order"
                    },
                    "fx:item_options": [
                        {
                            "_links": { },
                            "date_created": null,
                            "date_modified": null,
                            "name": "size",
                            "price_mod": 0,
                            "value": "Large",
                            "weight_mod": 0
                        }
                    ]
                },
                "_links": { },
                "base_price": 10.95,
                "code": "widget-sub",
                "date_created": null,
                "date_modified": "2018-03-01T00:00:00-0700",
                "delivery_type": "shipped",
                "discount_details": null,
                "discount_name": null,
                "discount_type": null,
                "downloadable_url": null,
                "expires": 0,
                "height": 0,
                "image": null,
                "is_future_line_item": false,
                "item_category_uri": "https://api.foxycart.com/item_categories/101",
                "length": 0,
                "name": "Widget Subscription",
                "parent_code": null,
                "price": 10.95,
                "quantity": 1,
                "quantity_max": 0,
                "quantity_min": 0,
                "shipto": null,
                "sub_token_url": "https://example.foxycart.com/cart?sub_token=127c8410bce9896f0d4a0950a3cf3796514e3fd2a8aa55476c3d0c5de184dbc7",
                "subscription_end_date": null,
                "subscription_frequency": "1m",
                "subscription_next_transaction_date": "2018-04-01T00:00:00-0700",
                "subscription_start_date": "2018-03-01T00:00:00-0700",
                "url": null,
                "weight": 1,
                "width": 0
            }
        ],
        "fx:payments": [
            {
                "_links": { },
                "amount": 54.2,
                "cc_exp_month": "05",
                "cc_exp_year": "2025",
                "cc_number_masked": "xxxxxxxxxxxx4242",
                "cc_type": "Visa",
                "date_created": "2018-03-01T00:00:00-0700",
                "date_modified": "2018-03-01T00:00:00-0700",
                "fraud_protection_score": 0,
                "gateway_type": "vantiv",
                "paypal_payer_id": null,
                "processor_response": "Vantiv Transaction ID: 123456789123456789",
                "processor_response_details": null,
                "purchase_order": null,
                "third_party_id": null,
                "type": "plastic"
            }
        ],
        "fx:shipments": [
            {
                "_links": { },
                "address1": "123 Test Street",
                "address2": "Level 2",
                "address_name": "Me",
                "city": "SAINT PAUL",
                "company": "Some Company",
                "country": "US",
                "date_created": null,
                "date_modified": "2018-03-01T00:00:00-0700",
                "first_name": "John",
                "last_name": "Smith",
                "phone": "123456789",
                "postal_code": "55115",
                "region": "MN",
                "shipping_service_description": "USPS Priority Mail 2-Day",
                "shipping_service_id": 14,
                "total_item_price": 37.9,
                "total_price": 54.2,
                "total_shipping": 17.85,
                "total_tax": 2.24
            }
        ]
    },
    "_links": { },
    "currency_code": "USD",
    "currency_symbol": "$",
    "customer_email": "john@example.com",
    "customer_first_name": "John",
    "customer_ip": "193.123.4.56",
    "customer_last_name": "Smith",
    "customer_tax_id": null,
    "data_is_fed": true,
    "date_created": null,
    "date_modified": "2018-03-01T00:00:00-0700",
    "display_id": 3456789,
    "hide_transaction": false,
    "id": 3456789,
    "ip_country": "United States",
    "is_new_subscription": true,
    "is_test": true,
    "locale_code": "en_US",
    "receipt_url": "https://example.foxycart.com/receipt?id=2a5175756293151099587e3b45590376",
    "status": "approved",
    "store_id": "12345",
    "store_version": "2.0",
    "total_discount": -3.79,
    "total_future_item_price": 0,
    "total_future_shipping": 0,
    "total_item_price": 37.9,
    "total_order": 54.2,
    "total_shipping": 17.85,
    "total_tax": 2.24,
    "transaction_date": "2018-03-01T00:00:00-0700",
    "transaction_type": "transaction"
    "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0"
}

There is also a few extra attributes included to help identify information about the transaction:

transaction_type
What type of transaction this was, could be transaction, updateinfo, subscription_modification, subscription_renewal or subscription_cancellation
is_new_subscription
A boolean value representing if a new subscription is included in this transaction or not.

Subscription Cancelled

{
    "_embedded": {
        "fx:customer": {
            "_links": { },
            "date_created": null,
            "date_modified": "2019-08-09T23:44:00-0700",
            "email": "john@example.com",
            "first_name": "John",
            "forgot_password": null,
            "forgot_password_timestamp": null,
            "id": 12345,
            "is_anonymous": false,
            "last_login_date": "2019-08-09T23:43:49-0700",
            "last_name": "Smith",
            "password_hash": "$2y$13$Jx7p31h18VnW.ab38QaYJu1QCzsyMQZKdY5s21nYewmGQj0OQCyIq",
            "password_hash_config": "13",
            "password_hash_type": "bcrypt",
            "password_salt": null,
            "tax_id": null
        },
        "fx:transaction_template": {
            "_embedded": {
                "fx:discounts": [
                    {
                        "_links": { },
                        "amount": -2,
                        "code": "coupon",
                        "date_created": null,
                        "date_modified": null,
                        "display": "-$2.00",
                        "is_future_discount": false,
                        "is_taxable": false,
                        "name": "Default Discount"
                    }
                ],
                "fx:items": [
                    {
                        "_embedded": {
                            "fx:item_category": {
                                "_links": { },
                                "admin_email": "john@example.com",
                                "admin_email_template_uri": null,
                                "code": "DEFAULT",
                                "customer_email_template_uri": null,
                                "customs_value": 0,
                                "date_created": null,
                                "date_modified": "2019-08-09T23:47:58-0700",
                                "default_length_unit": "IN",
                                "default_weight": 0,
                                "default_weight_unit": "LBS",
                                "discount_details": "1-10",
                                "discount_name": "Gift Set",
                                "discount_type": null,
                                "handling_fee": 1,
                                "handling_fee_minimum": 0,
                                "handling_fee_percentage": 10,
                                "handling_fee_type": "flat_percent_with_minimum",
                                "item_delivery_type": "flat_rate",
                                "max_downloads_per_customer": 3,
                                "max_downloads_time_period": 24,
                                "name": "Default for all products",
                                "send_admin_email": false,
                                "send_customer_email": false,
                                "shipping_flat_rate": 5,
                                "shipping_flat_rate_type": "per_order"
                            }
                        },
                        "_links": { },
                        "code": null,
                        "date_created": null,
                        "date_modified": "2019-08-10T00:40:14-0700",
                        "discount_details": null,
                        "discount_name": null,
                        "discount_type": null,
                        "expires": 0,
                        "height": 0,
                        "image": null,
                        "is_future_line_item": false,
                        "item_category_uri": "https://api.foxycart.com/item_categories/100",
                        "length": 0,
                        "name": "test",
                        "parent_code": null,
                        "price": 20,
                        "quantity": 1,
                        "quantity_max": 0,
                        "quantity_min": 0,
                        "shipto": "Me",
                        "subscription_end_date": "2019-08-11T00:00:00-0700",
                        "subscription_frequency": "1y",
                        "subscription_next_transaction_date": "2019-10-24T00:00:00-0700",
                        "subscription_start_date": "2017-10-24T00:00:00-0700",
                        "url": null,
                        "weight": 0,
                        "width": 0
                    }
                ]
            },
            "_links": { },
            "billing_address1": "123 Test Street",
            "billing_address2": null,
            "billing_city": "SAINT PAUL",
            "billing_company": "Some Company",
            "billing_country": "US",
            "billing_first_name": "John",
            "billing_last_name": "Smith",
            "billing_phone": null,
            "billing_postal_code": "5000",
            "billing_state": "MN",
            "customer_uri": "https://api.foxycart.com/customers/15460475",
            "date_created": null,
            "date_modified": "2019-08-10T00:40:14-0700",
            "language": null,
            "locale_code": "en_US",
            "shipping_address1": "123 Test Street",
            "shipping_address2": null,
            "shipping_city": "SAINT PAUL",
            "shipping_company": "Some Company",
            "shipping_country": "US",
            "shipping_first_name": "John",
            "shipping_last_name": "Smith",
            "shipping_phone": null,
            "shipping_postal_code": "5000",
            "shipping_state": "MN",
            "template_set_uri": null,
            "total_future_shipping": 0,
            "total_item_price": 20,
            "total_order": 25,
            "total_shipping": 5,
            "total_tax": 0,
            "use_customer_shipping_address": false
        }
    },
    "_links": { },
    "date_created": null,
    "date_modified": "2019-08-11T04:13:01-0700",
    "end_date": "2019-08-11T00:00:00-0700",
    "error_message": null,
    "first_failed_transaction_date": null,
    "frequency": "1y",
    "is_active": false,
    "next_transaction_date": "2019-10-24T00:00:00-0700",
    "past_due_amount": 0,
    "start_date": "2017-10-24T00:00:00-0700",
    "sub_token_url": "https://example.foxycart.com/cart?sub_token=b37fb078e12ba5ef86773b43e60bdea5124a46f4b5698a5387862cd82042d003c",
    "third_party_id": null
}

Validating the payload

Using the encryption key associated with each webhook, a signature is created of the payload that is passed with each request as a header, specifically as Foxy-Webhook-Signature. This signature can then be used on your endpoint to confirm that the request has come from a trusted source, in this case from Foxy.

To create a signature on your endpoint to compare, you generate a HMAC SHA256 hash of the data you've received, using the encryption key for the webhook as the key. The resulting signature should be in a hexadecimal format, and you would then perform a secure comparison of the two values to verify that the data is as it should be.

Example Endpoints

The following are some examples of code you can use for your Webhook endpoints, which include logic to validate the payload against the header signature value. The code assumes that your endpoint is secured with an SSL certificate - if you have an insecure endpoint, you'll need to review the next section on Handling Encrypted Payloads as well.

PHP

<?php
 
define('FOXY_WEBHOOK_ENCRYPTION_KEY', 'ABC123');
 
$data = file_get_contents('php://input');
$parsedData = json_decode($data, true);
$event = $_SERVER['HTTP_FOXY_WEBHOOK_EVENT'];
 
// Verify the webhook payload
$signature = hash_hmac('sha256', $data, FOXY_WEBHOOK_ENCRYPTION_KEY);
if (!hash_equals($signature, $_SERVER['HTTP_FOXY_WEBHOOK_SIGNATURE'])) {
    echo "Signature verification failed - data corrupted";
    http_response_code(500);
    return;
}
 
if (is_array($parsedData)) {
    // Handle the payload
 
    if ($event == "transaction/created") {
        // The following is an example of working with the transaction/created payload
        $email_address = $parsedData['customer_email'];
        $billing_country = $parsedData['_embedded']['fx:billing_addresses']['country'];
        $shipping_country = $parsedData['_embedded']['fx:shipments'][0]['country']; // Assuming single-ship
 
        $has_small_product_a = false;
        $has_large_product_a = false;
 
        foreach ($parsedData['_embedded']['fx:items'] as $item) {
            $name = $item['name'];
            $quantity = $item['quantity'];
            $category = $item['_embedded']['fx:item_category']['code'];
 
            if ($item['name'] == "Product A") {
                foreach($item['_embedded']['fx:item_options'] as $item_option) {
                    if ($item_option['name'] == "size") {
                        if ($item_option['value'] == "small") {
                            $has_small_product_a = true;
                        } else if ($item_option['value'] == "large") {
                            $has_large_product_a = true;
                        }
                    }
                }
            }
        }
    }
 
} else {
    // JSON data not found
    echo("No data");
    http_response_code(500);
    return;
}

Node

Note that you can also use the official Foxy SDK to make pieces of this easier, but this example involves minimal dependencies:

const crypto = require('crypto');
const foxyEncryptionKey = 'YOUR_ENCRYPTION_KEY_HERE';
const foxyStoreId = 'YOUR_STORE_ID_HERE';
const foxyStoreDomain = 'YOUR_STORE_DOMAIN_HERE';
 
const http = require('http');
const server = http.createServer(handleRequest);
 
const routes = {
  'transaction/created': handleTransactionCreated,
}
 
function handleRequest(request, response) {
  if (!validFoxyRequest(request)) {
    response.statusCode = 403; // Forbidden
    response.write('Forbidden');
    return response.end();
  }
  const bodyChunks = [];
 
  request.on('data', (chunk) => bodyChunks.push(chunk));
  request.on('end', () => {
    let body = Buffer.concat(bodyChunks).toString()
    if (!validFoxySignature(request.headers['foxy-webhook-signature'], body)){
      response.statusCode = 403; // Forbidden
      response.write('Forbidden');
      return response.end();
    }
    try {
      body = JSON.parse(body);
    } catch(e) {
      response.statusCode = 400; // Bad Request
      response.write('Bad Request');
      return response.end();
    }
    const foxyEvent = request.headers['foxy-webhook-event'];
    const responseData = routes[foxyEvent](request.headers, body);
    console.log(responseData);
    if (responseData) {
      response.statusCode = 200;
      response.write(JSON.stringify(responseData));
      return response.end();
    }
    response.statusCode = 500;
    response.end();
  });
}
 
function validFoxyRequest(request) {
  const postMethod = request.method === 'POST';
  const headers = request.headers;
  const routeForEventExists = Object.keys(routes).includes(headers['foxy-webhook-event']);
  const foxySignatureExists = !!headers['foxy-webhook-signature'];
  const foxyStoreIdIsCorrect = headers['foxy-store-id'] === foxyStoreId;
  const foxyStoreDomainIsCorrect = headers['foxy-store-domain'] === foxyStoreDomain;
  return postMethod && routeForEventExists && foxySignatureExists && foxyStoreIdIsCorrect && foxyStoreDomainIsCorrect;
}
 
function validFoxySignature(signature, payload) {
  const referenceSignature = crypto.createHmac('sha256', foxyEncryptionKey).update(payload).digest('hex');
  console.log(referenceSignature);
  return signature === referenceSignature;
}
 
function handleTransactionCreated(headers, body) {
  const emailAddress = body['customer_email'];
  const billingCountry = body['_embedded']['fx:billing_addresses']['country'];
  const shippingCountry = body['_embedded']['fx:shipments'][0]['country'];
  // Check if there is a Product A and its size
  let hasSmallA = false;;
  let hasLargeA = false;;
  for (let item of body['_embedded']['fx:items']) {
    const name = item['name'];
    const quantity = item['quantity'];
    const category = item['_embedded']['fx:item_category']['code'];
    if(item['name'] === "Product A") {
      for (let itemOption of item['_embedded']['fx:item_options']) {
        if (itemOption['name'] == 'size') {
          if (itemOption['value'] === 'small') {
            hasSmallA = true;
            return {
              ok: true,
              details: "has small A"
            }
          } else if (itemOption['value'] === 'large') {
            hasLargeA = true;
            return {
              ok: true,
              details: "has large A"
            }
          }
        }
      }
    }
  }
}
 
server.listen(80);

Ruby

This simple Ruby example is using a small Sinatra app - some logic may need to be adjusted if you're using a different framework.

require 'sinatra'
require 'json'
 
def verify_webhook(data, encryption_key)
    signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), encryption_key, data)
    return halt 500, "Signature verification failed - data corrupted" unless Rack::Utils.secure_compare(signature, request.env['HTTP_FOXY_WEBHOOK_SIGNATURE'])
end
 
post '/webhook' do
    request.body.rewind
    data = request.body.read
    event = request.env['HTTP_FOXY_WEBHOOK_EVENT']
    verify_webhook(data, ENV['FOXY_WEBHOOK_ENCRYPTION_KEY'])
 
    parsedData = JSON.parse(data)
 
    # Handle the payload
    puts parsedData['id']
end

Important Notes

  • You should avoid storing your Webhook encryption key directly within your codebase. Instead, store it as an environment variable for your application
  • When comparing your locally created hmac signature to the one from the request header, avoid doing a direct equality comparison like ==. Instead, use a more secure method of comparison that comes with your coding language of choice, such as secure_compare() in Ruby and hash_equals() in PHP 5.6+. If you don't have access to PHP 5.6+, you can use one of the user supplied polyfill functions on this page.

Handling encrypted payloads

While we strongly recommend setting a webhook endpoint that is secured with an SSL certificate - we do also support insecure endpoint URL's as well. If you're using an insecure endpoint URL, instead of being passed the raw payload - it will be sent encrypted using AES-256-CBC, and as such will need to be decrypted on your endpoint before you can work with it.

The payload is sent with three hexadecimal values representing the MAC (Message Authentication Code), IV (Initialization Vector) and the encrypted data, concatenated by a : character. To decrypt the payload, you can follow these steps:

  1. Split the payload into it's three pieces by the : character
  2. Confirm the validity of the payload by completing a secure comparison of the MAC value to a HMAC SHA256 digest of the IV and data values concatenated together.
  3. Convert the IV value from hexadecimal to binary
  4. Create a binary SHA256 hash of the webhook encryption key
  5. Decrypt the data as AES-256-CBC using the IV and key binary values you just created
  6. Parse the decrypted data as JSON

The following is some example implementations of performing those steps:

PHP

<?php
 
$parts = explode(':', $data);
$mac = $parts[0];
$iv = $parts[1];
$data = $parts[2];
 
$calc_mac = hash('sha256', "$iv:$data");
 
if (hash_equals($calc_mac, $mac)) {
    $iv = hex2bin($iv);
    $key = hex2bin(hash('sha256', FOXY_WEBHOOK_ENCRYPTION_KEY));
 
    if ($data = openssl_decrypt($data, 'aes-256-cbc', $key, 0, $iv)) {
        $parsedData = json_decode($data, true);
    } else {
        while ($msg = openssl_error_string()) {
            echo("Openssl error: " . $msg);
        }
        http_response_code(500);
        return;
    }
} else {
    // Encrypted data corrupted
    echo("Encrypted data corrupted");
    http_response_code(500);
    return;
}

Ruby

mac, iv, data = data.split(":")
calc_mac = Digest::SHA256.hexdigest("#{iv}:#{data}")
 
if Rack::Utils.secure_compare(calc_mac, mac)
    begin
        data = Base64::decode64(data)
        decipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
        decipher.decrypt
        decipher.key = Digest::SHA256.digest(ENV['FOXY_WEBHOOK_ENCRYPTION_KEY'])
        decipher.iv = iv.scan(/../).map { |x| x.hex.chr }.join
        data = decipher.update(data) + decipher.final
    rescue OpenSSL::Cipher::CipherError
        halt(500, "Unable to decrpyt data")
    end
else
    halt(500, "Encrypted data corrupted")
end

Zapier

Zapier.com is an automation service that allows you to create complex workflows that are executed when specific events occur. Zapier connects with hundreds of different services, and features a visual workflow creation tool allowing you to easily automate tasks and free up time.

Currently Foxy's integration with Zapier has one supported trigger, which is for each new transaction.

To make use of our Zapier integration, you will need to create your zaps within the Zapier administration and select our app from the list of trigger apps.

Foxy's Zapier integration is currently in private beta. If you would like access to the beta and be one of the first to try it out, please get in touch with us.

Once you've selected our app - simply follow the steps within Zapier's visual workflow editor to select the trigger, test the connection and build out the rest of your workflow. When setting up your FoxyCart trigger, it will attempt to fetch the latest transaction for your store to use as sample data when building out the rest of your zap. If your store doesn't have any transactions, you will need to complete a test transaction before completing the setup for your zap.

Webflow

Our Webflow webhook connects your FoxyCart store to your Webflow sites to introduce support for inventory management. This integration may be expanded in the future to support additional functionality as the Webflow API is expanded.

Setting up your Webflow collections

Before connecting Foxy to your Webflow site - you first need to create the necessary product collection within the Webflow CMS. For details on how to create collections - you can follow Webflow's guide on that here.

We also have tutorials and a starter template available for building a responsive ecommerce website with Webflow and Foxy - which you can review here. The information there will help you get you set up and ready to go for this integration.

If you're creating your own collections, when creating the Webflow collection for your products - there are some options you'll need as a minimum to work with Foxy, and we would recommend setting all of these options to also be required fields for creating products.

  1. A plain text field for the product name
  2. A number field for the product price
  3. A plain text field for the product code
  4. A number field for the product inventory, set to allow negative numbers

The name and price fields are the minimum values required to add a product to the cart, and the code and inventory fields are utilised for updating the inventory. If you don't have unique codes you can assign for each of your products, you could leave off the “code” field in your product collection, and instead utilise the “slug” attribute that Webflow automatically assigns to each of your products as the unique identifier in a later step.

Important to note, this integration assumes that all of your products are set up within a single Webflow collection, and that all of your products will be tracked for inventory. If the integration is unable to find a specific product code from an order in your products collection, it will trigger an error response for the webhook.

Customising your add to carts

When creating your add to cart links or forms within Webflow, you need to ensure that the Foxy integration knows which product it should update inventory for when it's processing a transaction. To do that, you'll need to ensure that you include the “code” value as part of your add to cart. You can also add an additional value to your form using the name quantity_max, and assign the value for that to be the “inventory” level for your product. This way, a customer is not able to order more of an item than you currently have.

If you used the ecommerce template linked to above, the template already includes the “code” attribute for you in the link within the products template - but if you're doing something custom, you can follow these steps to add it in.

If your products are added to the cart through a link, the additional parameters for your add to cart URL would be like this: &code=[ WEBFLOW_CODE ]&quantity_max=[ WEBFLOW_INVENTORY ].

If you are using a form, the additional fields would instead look like this:

<input type="hidden" name="code" value="[ WEBFLOW_CODE ]">
<input type="hidden" name="quantity_max" value="[ WEBFLOW_INVENTORY ]">

You will need to change both of those placeholder strings to the dynamic Webflow fields within the editor. The value for the [ WEBFLOW_CODE ] placeholder will either be the “code” field specified on the product collection, or if you didn't create one, use the “slug” attribute of your product that Webflow creates. For [ WEBFLOW_INVENTORY ], you'll set that to the “inventory” field you created for your products collection.

Once you've got the products collection created and the add to cart links added to your site, we'd recommend adding some products to the collection if you haven't already just so you have something to test with.

Enabling the Webflow webhook

Once you've got your collection created, you can then connect Foxy to your Webflow site by following these steps:

  1. On the Integrations section of your store's FoxyCart administration, check the checkbox for “Webflow” within the Webhooks section, and click the “Integrate Webflow”.
  2. You will be redirected to Webflow where you will need to login (if not already) using your Webflow login, and select which project you want to connect to Foxy.
  3. After selecting the desired project, you will be redirected back to the Foxy integrations page.
  4. From the dropdown within the Webflow section, select the Webflow collection for your products that you created above and click “Update”
  5. Next, select the code and inventory fields that you created earlier, and click “Update”. If you didn't create a code field, select the “slug” option instead.

Congratulations, you have now connected your Foxy store to Webflow! For any transactions completed in the future, each product in the cart will have the inventory value decreased in the matching item in the Webflow products collection (based on the product code).

Webflow webhook errors

Beyond the webhook errors noted earlier on this page, there are also errors specific to the Webflow integration that you may experience.

Product code ... not found

Specifically, if you have a product in a transaction which is missing a code attribute, or has a code attribute set which doesn't match an item in your products collection, you'll see an error like this within your store's error log:

Webhooks processing problems:
Your Site Name (webflow): Product code "my-missing-code" not found [400]

If you experience this error, you'll first want to confirm where the product was added to the cart and ensure that the link/form has the correct code associated with the product. To be able to re-feed the webhook to Webflow, you will need to update the product in the CMS to have the matching code to how it was sent.

If the product code in the transaction matches the product in your collection, you will also want to confirm that the configured settings for your Webflow connection on the “Integrations” page in the Foxy administration is set to the correct field from your collection for the code. For example, if you're using the slug field from your collection as the product code, then that should also be set in the same way on the “Integrations” page in the Foxy admin. If it's not correct, you will need to disable the connection and re-connect, ensuring you select the right fields from your collection.

This item can't be published / CollectionSchemaChangedError

If you've edited your product collections schema without publishing the page, Webflow will prevent any publishing requests via the API until the changes are published. If you experience this error, you'll see an error like this within your store's error log:

Webhooks processing problems:
Your Site Name (webflow): Conflict: This item can't be published, as your Collection structure changed since the last site publish. Make sure your staging and live Collections match to continue.
Webhooks processing problems:
Webflow (webflow): CollectionSchemaChangedError: The collection structure changed since the last publish [400]

To fix these types of errors, simply ensure any changes to your collection's schemas have been published within Webflow, and then refeed the webhook to your Webflow site.

Site Tools