Webhooks

Webhooks provide a way to retrieve and use information when certain events happen in your LemonStand store. They allow you to register an https:// endpoint where the event data can be sent in the JSON format. Webhooks allow developers to build apps that receive information, in near real-time, about events that happen in LemonStand stores and enable them to extend, customize, and integrate LemonStand with their own custom extensions or even with other applications around the web. They are a way to automatically send valuable information when it happens, rather than requiring continual polling for data.

Webhooks require you to provide a URI, called the destination URI, specifying where you want information about the events that your application subscribes to sent. When the event happens we'll send an HTTPS POST request to your callback URI and then your app can perform some action based on that event.

You can create and update your Webhooks configuration by navigating in your LemonStand store admin to Integrations > Webhooks. See the Tutorial tab on this page for more details on how to do this.

You can also manage Webhooks via the LemonStand API, click here for documentation on Webhooks API usage.

Prerequisites For Use

You must have a valid TLS/SSL (Transport Layer Security/Secure Sockets Layer) set up for the server endpoint where the data is being sent. You can verify your set up at the following site: https://sslcheck.globalsign.com. Any one of the following will cause a connection failure: hostname/DNS mismatch, self-signed certificate, and/or intermediate certificates not loaded.

Event Triggers

LemonStand has a number of predefined events that you can use to trigger a Webhook which are described below. If there is an event you would like to trigger a Webhook for that is not in the list below please contact suport@lemonstand.com.

Entity Event Description
Customers customer_created A customer is created at time of registration, or when an order is completed if the customer is a guest.
Orders order_created An order is created after the shop:onPay action is used. An order is created even if a payment fails.
Orders order_updated When any update is made to an order, including status updates.
Orders order_status_updated Only when the status of an order is updated, excludes other updates such as updating order items, etc.
Orders order_placed Triggered after the invoices, payments and shipments are created. This occurs during the checkout process before a payment is processed.
Orders order_deleted
When an order is deleted.
Orders order_paid Whenever a payment is completed. This includes custom payment methods that do not actually collect funds, but rather are used as a placeholder until payment is received. An example of this is a money order. This event is triggered whenever the shop:onPay method is used.
Orders order_mark_paid Whenever a payment from an authorized source (PayPal, Authorize.NET, Braintree, BitPay, Stripe, etc.) is processed as successful.

We have included detailed step by step instructions on how to create and update Webhooks in your LemonStand store, as well as more technical details on the Cheat Sheet tab.

How To Create a Webhook

  1. Navigate to the Integrations > Webhooks page in your LemonStand admin.
  2. Click the Add button.
  3. Select the Webhook event type you want to listen for from the drop-down box.
  4. Enter the Destination URL (https://) where you want to receive notifications.
  5. If your application used Basic Authentication, enter the username and password.
  6. Use the Test Webhook button to send a sample request from your destination and confirm a valid response.

See the Cheat Sheet for technical details on Webhooks payloads, headers, timeouts, retries and more.

How To Update a Webhook

  1. Navigate to the Integrations > Webhooks page in your LemonStand admin.
  2. Click on the Webhook event you want to update from the list.
  3. Update any form fields you need to change.
  4. Use the Test Webhook button to send a sample request from your destination and confirm a valid response.

Additional technical detailsand code snippets on Webhooks are shown below.

Payload

The body of the request will depend on what event your application is listening for. The format of the request body used for webhooks is identical to the response body used in our API. For example, all order events triggered are equivalent to making this api call:

https://<store>.lemonstand.com/api/v2/orders?embed=customer,invoices.payments.transactions,invoices.payments.attempts,invoices.payments.billing_address,invoices.shipments.tracking_codes,invoices.shipments.items,invoices.shipments.shipping_address</store>

The body of the order event request is shown below:

{
  "meta": {
    "status": 200,
    "success": true
  },
  "data": {
    "id": 150,
    "shop_order_id": null,
    "shop_order_status_id": 5,
    "shop_customer_id": 110,
    "status": "Paid",
    "number": 334571,
    "is_quote": 1,
    "is_tax_exempt": 0,
    "total": 1683.36,
    "total_invoiced": 1683.36,
    "total_paid": 1683.36,
    "total_refunded": null,
    "subtotal_invoiced": 1499,
    "subtotal_paid": 1499,
    "subtotal_refunded": null,
    "total_discount": 0,
    "total_sales_tax": 179.88,
    "total_sales_tax_invoiced": 179.88,
    "total_sales_tax_paid": 179.88,
    "total_sales_tax_refunded": null,
    "total_shipping_tax": 0.48,
    "total_shipping_tax_invoiced": 0.48,
    "total_shipping_tax_paid": 0.48,
    "total_shipping_tax_refunded": null,
    "total_shipping_quote": 4,
    "total_shipping_invoiced": 4,
    "total_shipping_paid": 4,
    "total_shipping_refunded": null,
    "status_updated_at": null,
    "created_by": null,
    "updated_by": null,
    "created_at": "2014-12-29T13:13:42-0800",
    "updated_at": "2014-12-29T13:13:46-0800",
    "customer": {
      "data": {
        "id": 110,
        "first_name": "Chrs",
        "last_name": "Zieba",
        "email": "chris@lemonstand.com",
        "notes": null,
        "is_guest": 1,
        "created_by": null,
        "updated_by": null,
        "created_at": "2014-12-29T13:13:42-0800",
        "updated_at": "2014-12-29T13:13:42-0800"
      }
    },
    "invoices": {
      "data": [
        {
          "id": 132,
          "shop_order_id": 150,
          "number": 1,
          "shop_invoice_status_id": 2,
          "status": "Paid",
          "subtotal": 1499,
          "total_shipping_quote": 4,
          "tax_total": 180.36,
          "total_discount": 0,
          "total": 1683.36,
          "created_by": null,
          "updated_by": null,
          "created_at": "2014-12-29T13:13:43-0800",
          "updated_at": "2014-12-29T13:13:46-0800",
          "payments": {
            "data": [
              {
                "id": 132,
                "shop_invoice_id": 132,
                "number": 1,
                "shop_payment_status_id": 2,
                "amount": 1683.36,
                "description": null,
                "hash": "e8db80999dedcad59827b1094d9e97de",
                "processed_at": "2014-12-29T13:13:46-0800",
                "created_by": null,
                "updated_by": null,
                "created_at": "2014-12-29T13:13:43-0800",
                "updated_at": "2014-12-29T13:13:46-0800",
                "billing_address": {
                  "data": {
                    "id": 570,
                    "first_name": "Chrs",
                    "last_name": "Zieba",
                    "phone": null,
                    "company": null,
                    "street_address": "912-525 Seymour Street",
                    "city": "Vancouver",
                    "postal_code": "V6B 3H7",
                    "country": "Canada",
                    "country_code": "CA",
                    "state": "British Columbia",
                    "state_code": "BC",
                    "is_default": 0,
                    "is_billing": 1,
                    "is_business": 0,
                    "created_at": "2014-12-29T13:13:43-0800",
                    "updated_at": "2014-12-29T13:13:43-0800"
                  }
                },
                "transactions": {
                  "data": [
                    {
                      "id": 2,
                      "shop_payment_method_id": 3,
                      "shop_payment_id": 132,
                      "transaction_id": "ch_4Q0ronMbxx2Agx",
                      "status_name": "Paid",
                      "status_code": "paid",
                      "user_note": null,
                      "created_by": null,
                      "updated_by": null,
                      "created_at": "2014-12-29T13:13:46-0800",
                      "updated_at": "2014-12-29T13:13:46-0800"
                    }
                  ]
                },
                "attempts": {
                  "data": [
                    {
                      "id": 122,
                      "shop_payment_method_id": 3,
                      "shop_payment_id": 132,
                      "is_successful": 1,
                      "message": "Successful payment",
                      "is_test": 0,
                      "required_fields": null,
                      "response_fields": {
                        "object": "charge",
                        "created": 1419887626,
                        "livemode": false,
                        "paid": true,
                        "amount": 168336,
                        "currency": "cad",
                        "refunded": false,
                        "captured": true,
                        "card": {
                          "object": "card",
                          "last4": "1111",
                          "brand": "Visa",
                          "funding": "unknown",
                          "exp_month": 11,
                          "exp_year": 2019,
                          "fingerprint": "5xqHCOwJQLa8eqL0",
                          "country": "US",
                          "name": "ssdf sdfsdf",
                          "address_line1": "912-525 Seymour Street",
                          "address_line2": null,
                          "address_city": null,
                          "address_state": "BC",
                          "address_zip": "V6B 3H7",
                          "address_country": null,
                          "cvc_check": "pass",
                          "address_line1_check": "pass",
                          "address_zip_check": "pass",
                          "dynamic_last4": null,
                          "customer": null,
                          "type": "Visa"
                        },
                        "balance_transaction": {
                          "object": "balance_transaction",
                          "amount": 141854,
                          "currency": "usd",
                          "net": 137710,
                          "type": "charge",
                          "created": 1419887626,
                          "available_on": 1420416000,
                          "status": "pending",
                          "fee": 4144,
                          "fee_details": [
                            {
                              "amount": 4144,
                              "currency": "usd",
                              "type": "stripe_fee",
                              "description": "Stripe processing fees",
                              "application": null
                            }
                          ],
                          "source": "ch_5Q0ronMbxx4Agx",
                          "description": null
                        },
                        "failure_message": null,
                        "failure_code": null,
                        "amount_refunded": 0,
                        "customer": null,
                        "invoice": null,
                        "description": null,
                        "dispute": null,
                        "metadata": [],
                        "statement_descriptor": null,
                        "fraud_details": [],
                        "receipt_email": null,
                        "receipt_number": null,
                        "shipping": null,
                        "statement_description": null,
                        "fee": 4144,
                        "fee_details": [
                          {
                            "amount": 4144,
                            "currency": "usd",
                            "type": "stripe_fee",
                            "description": "Stripe processing fees",
                            "application": null
                          }
                        ]
                      },
                      "raw_response": {
                        "object": "charge",
                        "created": 1419887626,
                        "livemode": false,
                        "paid": true,
                        "amount": 168336,
                        "currency": "cad",
                        "refunded": false,
                        "captured": true,
                        "card": {
                          "object": "card",
                          "last4": "1111",
                          "brand": "Visa",
                          "funding": "unknown",
                          "exp_month": 11,
                          "exp_year": 2019,
                          "fingerprint": "x",
                          "country": "US",
                          "name": "Chris Zieba",
                          "address_line1": "912-525 Seymour Street",
                          "address_line2": null,
                          "address_city": null,
                          "address_state": "BC",
                          "address_zip": "V6B 3H7",
                          "address_country": null,
                          "cvc_check": "pass",
                          "address_line1_check": "pass",
                          "address_zip_check": "pass",
                          "dynamic_last4": null,
                          "customer": null,
                          "type": "Visa"
                        },
                        "balance_transaction": {
                          "object": "balance_transaction",
                          "amount": 141854,
                          "currency": "usd",
                          "net": 137710,
                          "type": "charge",
                          "created": 1419887626,
                          "available_on": 1420416000,
                          "status": "pending",
                          "fee": 4144,
                          "fee_details": [
                            {
                              "amount": 4144,
                              "currency": "usd",
                              "type": "stripe_fee",
                              "description": "Stripe processing fees",
                              "application": null
                            }
                          ],
                          "source": "ch_5Q0ronMbxx4Agx",
                          "description": null
                        },
                        "failure_message": null,
                        "failure_code": null,
                        "amount_refunded": 0,
                        "customer": null,
                        "invoice": null,
                        "description": null,
                        "dispute": null,
                        "metadata": [],
                        "statement_descriptor": null,
                        "fraud_details": [],
                        "receipt_email": null,
                        "receipt_number": null,
                        "shipping": null,
                        "statement_description": null,
                        "fee": 4144,
                        "fee_details": [
                          {
                            "amount": 4144,
                            "currency": "usd",
                            "type": "stripe_fee",
                            "description": "Stripe processing fees",
                            "application": null
                          }
                        ]
                      },
                      "avs_response_code": "pass / pass",
                      "avs_response_text": null,
                      "ccv_response_code": "pass",
                      "ccv_response_text": null,
                      "created_by": null,
                      "updated_by": null,
                      "created_at": "2014-12-29T13:13:45-0800",
                      "updated_at": "2014-12-29T13:13:46-0800"
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    },
    "items": {
      "data": [
        {
          "id": 165,
          "shop_order_id": 150,
          "shop_product_id": 39,
          "shop_tax_class_id": 2,
          "name": "skateboard",
          "description": null,
          "quantity": 1,
          "original_price": 1499,
          "price": 1499,
          "base_price": 1499,
          "cost": null,
          "discount": null,
          "total": 1499,
          "options": null,
          "extras": null
        }
      ]
    }
  }
}

Headers

X-LemonStand-Event

This header will let you know what event has been triggered. See the events section for possible values.

X-LemonStand-Signature

This header contains a signature of the request body. It can be used to verify the integrity of the requests from LemonStand. You can find the key used to signed the requests in the webhooks section of the backend.

Timeouts

The timeout set on a requests is 5 seconds. If a request has not received a response with code 200 after 5 seconds, it is marked as failed.

Retries

Failed requests are sent to a queue where they will be retried every 10 minutes for 12 hours. If the webhook is still not successful after the retry interval, it will be disabled. If a webhook is updated in the backend, any pending retries (from a failed webhook) will be cancelled.

Security

All requests from LemonStand will have a signature header which you can use to verify the validity of the request. The body of the request can be hashed with the webhooks key and then compared against the signature in the header.

<?php
    // This key can be found in the webhooks section of the backend.
    define('LEMONSTAND_WEBHOOK_KEY', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
    // The signature header that is accompanied with every request from LemonStand.
    $header = $_SERVER['HTTP_X_LEMONSTAND_SIGNATURE'];
    // The contents of the post body.
    $data = file_get_contents('php://input');
    // Calculate the hmac.
    $hmac = base64_encode(hash_hmac('sha256', $data, LEMONSTAND_WEBHOOK_KEY, true));
    if (hash_equals($header, $hmac)) {
      // The webhook is valid.
    }
?>

The above example uses PHP, your language and server implementations may differ from this code. There are a couple of very important thing to point out:

  • Using a plain == operator is not advised. A method like hash_equals (PHP > 5.6) performs a "constant time" string comparison, which renders it safe from certain timing attacks against regular equality operators. If using a version of PHP less than 5.6, you can still implement a comparison function which mitigates timing attacks, see here: https://gist.github.com/kAworu/9a97ac4ecd418dac3436 for an example.

Respond to a Webhook

Your application needs to respond with a 200 within 5 seconds in order to be marked as successful. Any response outside of the 200 range will let LemonStand know that you did not receive your webhook. We wait 5 seconds for a response to each request, and if there isn't one or we get an error, we retry the connection over the next 12 hours. If you're receiving a LemonStand webhook, the most important thing to do is respond quickly.

Testing

You can test your webhooks in the backend of your store on the specific webhook entry. There are many tools out there that can make working with webhooks during development much easier. A few examples include: RequestBin, Pagekite and ngrok.