# Webhooks

We currently support a couple of different webhooks, which can be configure on our end. Please reach out if you need us to set them up.

## Configuration Information

To setup a webhook, you will need to provide us with the following information:

* Webhook URL
* Auth Token
* The store(s) that the Webhook will be triggered for

## Authentication

Our webhooks send an authentication header in the following form. This can be configured when we setup the webhook.

```
"Authorization": "Bearer {Token}"
```

## Webhook Signature Verification

Our webhooks are signed using a hash-based message authentication code ([HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code)) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2).

To ensure request authenticity, you can validate their signature using the included headers.

### Webhook Headers

Every webhook request includes:

* `X-Jane-Signature`: The HMAC-SHA256 signature for the raw request body.
* `X-Jane-Request-Timestamp`: The timestamp used to generate the signature.

### How the Signature is Generated

The signature is generated using a combination of:

1. The raw request body.
2. The `X-Jane-Request-Timestamp` header.
3. The signing secret.

{% hint style="info" %}
**Signing secrets** are unique to each webhook subscription and are generated when the subscription is set up.
{% endhint %}

### Signature Validation Steps

#### **1. Compute the HMAC-SHA256 Digest**

Use the `X-Jane-Request-Timestamp` and raw request body, combined with the signing secret. For example, in Ruby on Rails:

```ruby
signing_secret = Rails.application.credentials.dig(:jane, :signing_secret)

signature = request.headers["X-Jane-Signature"]
timestamp = request.headers["X-Jane-Request-Timestamp"]
raw_request_body = request.body.read

# Combine timestamp and body
data = "#{timestamp}:#{raw_request_body}"
digest = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, data)

# Compare the computed digest to the signature
valid = ActiveSupport::SecurityUtils.secure_compare(
  signature,
  digest
)
```

#### 2. Compare Signatures

If the computed HMAC digest matches the `X-Jane-Signature` header, the request is valid and originates from Jane.

#### Notes

* **Raw Body Parsing**:

  Use the raw request body before any middleware (e.g., JSON parsers) processes it. Parsing or modifying the body can invalidate the signature.
* **Timestamp Validation**:

  Reject requests if the `X-Jane-Request-Timestamp` is too old (e.g., older than 5 minutes) to prevent replay attacks.

## Available Webhooks

### Cart Abandoned

#### When is the abandoned cart webhook triggered?

The abandoned webhook is triggered when all of the following conditions are met:

* There is at least one item in the cart
* The cart has not been checked out
* Six hours have elapsed since the first item was placed in the cart

#### Post body schema

You can expect the webhook post body to conform to the following schema:

```json5
{
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    webhook_event: {
      const: "cart_abandoned"
    },
    customer: {
      oneOf: [
        {
          type: "object",
          properties: {
            email: {
              type: [
                "string",
                "null"
              ]
            },
            first_name: {
              type: [
                "string",
                "null"
              ]
            },
            last_name: {
              type: [
                "string",
                "null"
              ]
            },
            phone_number: {
              type: [
                "string",
                "null"
              ]
            },
            DOB: {
              type: [
                "string",
                "null"
              ]
            }
          },
          required: [
            "email",
            "first_name",
            "last_name",
            "phone_number",
            "DOB"
          ]
        },
        {
          type: "null"
        }
      ]
    },
    store: {
      type: "object",
      properties: {
        id: {
          type: "integer"
        },
        name: {
          type: "string"
        },
        display_name: {
          type: [
            "string",
            "null"
          ]
        },
        store_url: {
          type: [
            "string",
            "null"
          ]
        }
      },
      required: [
        "id",
        "name",
        "display_name",
        "store_url"
      ]
    },
    cart: {
      type: "object",
      properties: {
        uuid: {
          type: "string",
          format: "uuid"
        },
        created_at: {
          type: "string",
          format: "date-time"
        },
        products: {
          type: "array",
          items: {
            type: "object",
            properties: {
              product_id: {
                type: "integer"
              },
              product_name: {
                type: "string"
              },
              product_brand_name: {
                type: "string"
              },
              product_image: {
                type: "array",
                items: {
                  type: "string",
                  format: "uri"
                }
              },
              product_price: {
                type: "string",
                examples: [
                  "4.20"
                ]
              },
              quantity: {
                type: "integer"
              },
              category: {
                enum: [
                  "flower",
                  "edible",
                  "extract",
                  "merch",
                  "grow",
                  "tincture",
                  "gear",
                  "topical",
                  "pre-roll",
                  "vape"
                ]
              },
              subcategory: {
                type: [
                  "string",
                  "null"
                ]
              },
              lineage: {
                type: [
                  "string",
                  "null"
                ]
              },
              weight_variant: {
                type: [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      required: [
        "uuid",
        "created_at",
        "products"
      ]
    }
  },
  required: [
    "webhook_event",
    "customer",
    "store",
    "cart"
  ]
}

```

#### Sample webhook body

```json
{
  "webhook_event": "cart_abandoned",
  "customer": {
    "email": "user4@example.com",
    "first_name": "Foo",
    "last_name": "Bar",
    "phone_number": "+13555555566",
    "DOB": "1990-05-01T00:00:00.000Z"
  },
  "store": {
    "id": 8,
    "name": "Store 8",
    "display_name": "PotShop",
    "store_url": "potshop.com"
  },
  "cart": {
    "uuid": "442c4c79-f244-4175-b358-e770bcaaf252",
    "created_at": "2022-12-08T17:13:41.077Z",
    "products": [
      {
        "product_id": 4,
        "product_name": "Product 4",
        "product_brand_name": "Brand 4",
        "product_image": [
          "https://s3.aws.com/uploads/17010258-8d0e-40f5-89cb-fe68872fd818.png"
        ],
        "product_price": "4.20",
        "quantity": 1,
        "category": "flower",
        "subcategory": "fake-brand-subtype",
        "lineage": "sativa",
        "weight_variant": "half_ounce"
      }
    ]
  }
}
```

### Cart Status Updated

#### When is the cart status updated webhook triggered?

The cart status updated webhook is triggered when a cart status changes (e.g. it is moved `verification` to `preparing`).

```json
{
  "cart": {
    "amount": 0.0,
    "assigned_employee_id": null,
    "assigned_employee_name": null,
    "assigned_manager_id": null,
    "assigned_manager_name": null,
    "checked_out_time": "",
    "checkout_message": null,
    "customer_phone_number": "+15555555555",
    "customer_email_address": "foo@foo.com",
    "customer_name": null,
    "created_at": "2023-07-13 21:01:18",
    "id": 56285,
    "pos_order_id": null,
    "promo_code": null,
    "queue_number": null,
    "rating": 0.0,
    "reservation_mode": "pickup",
    "reservation_start_window": "",
    "status": "cancelled",
    "store_id": 141,
    "store_name": "Store",
    "tags": null,
    "user_id": 1121,
    "external_user_id": null,
    "external_user_source": null,
    "uuid": "2314fcb3-4e87-4de0-b5d0-3bb7119799b5",
    "products": [
      {
        "brand": "Jane's Brew",
        "checkout_price": 13.0,
        "count": 3,
        "discounted_checkout_price": 13.0,
        "kind": "edible",
        "kind_subtype": "Bottled Tea",
        "name": "Green Tea (100mg)",
        "price_id": "each",
        "product_id": 2084,
        "special_id": null,
        "special_title": null,
        "lineage": "edible"
      }
    ]
  },
  "webhook_event": "cart_status_updated"
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.iheartjane.com/jane-docs/implementing-roots/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
