Concepts > Webhooks
OverviewConceptsWebhooks

Webhooks

General

Webhooks notify your app when specific events occur in Lana, eliminating the need to continuously poll the API. When set up, webhooks send either all or selected events to an HTTP endpoint in the form of a JSON payload. The general format of a message is as follows:

interface WebhookData {
  event_id: string;
  event_type: string;
  created_at: string;
  payload: Payload;
}
NOTE

Do not trust the origin of a webhook without verification — forgeries are possible. Perform an authenticated API request using the IDs in the webhook to verify the event's authenticity.

By design, Lana does not include any personally identifiable information in webhooks.

When Lana triggers a webhook event, it carries out a POST request to a URL specified in the webhook. This request is attempted thrice in a row with a timeout of 30 seconds and pauses of 1 second and 5 seconds between attempts. This sequence counts as a single attempt.

The webhook is considered successful only if the response code falls within the 2xx range. If all three requests fail, Lana retries delivery later. The following is a rough schedule for further retry attempts (±5 minutes):

  • 30 minutes
  • 60 minutes
  • 90 minutes
  • 2 hours
  • 3 hours
  • 4 hours
  • 8 hours
  • 16 hours
  • 24 hours
  • 36 hours

Challenge-Response Check (CRC) for Webhooks

Overview

Challenge-Response Check (CRC) is a security feature that ensures the authenticity and integrity of the communication between the webhook sender and the webhook handler.

Webhook Fields

Each webhook API object contains two fields:

  1. crc_status: Represents the status of the webhook's CRC. It can have the following values:

    • pending: The initial state of all newly created webhooks. Webhooks in this state can still receive events.
    • failed: Indicates that the webhook has failed the CRC. Webhooks in this state will not receive new events but may still receive retries for old events.
    • ok: Indicates that the webhook has successfully passed the CRC and can receive events normally.
  2. secret: A string that serves as the "secret" portion of the HMAC (Hash-based Message Authentication Code) used for CRC and webhook content validation.

CRC Check Process

  1. The CRC check is performed by sending a GET request to the webhook endpoint with the query parameter ?crc_token=<random>, where <random> is a random token.

  2. The webhook handler must respond to this GET request within 3 seconds with a JSON body containing the response_token field. The value of response_token should be the HMAC SHA256 hash of the crc_token, base64-encoded. The crc_token should be used as the "message" portion of the HMAC, without any encoding.

    Example response:

    {
      "response_token": "sha256=<hmac_sha256_hash_base64_encoded>"
    }
    

    In Go the correct GET request handler looks like this:

    const webhookSecret = `74e3f83b8b73f456ac0d731aecf73e8b39a6aceecd67c544eae124cd08cd26ad`;
    func handleCRC(w http.ResponseWriter, r *http.Request) {
      token := r.URL.Query().Get("crc_token")
      hmacHash := hmac.New(sha256.New, []byte(webhookSecret))
      hmacHash.Write([]byte(token))
      w.Header().Set("Content-Type", "application/json")
      w.WriteHeader(200)
      w.Write([]byte(`{"response_token":"sha256=` + base64.StdEncoding.EncodeToString(hmacHash.Sum(nil)) + `"}`))
    }
    
  3. The CRC check is performed every hour.

  4. If the webhook endpoint fails the CRC for 6 consecutive hours, the webhook will transition from the ok state to the failed state. All other state transitions are immediate.

Webhook Content Validation

In addition to the CRC, the secret can be used to validate the webhook POST body and the GET request sent during the CRC. The X-Webhook-Signature header is set on webhook requests, containing the HMAC SHA256 hash (base64-encoded) of the request body or the crc_token query parameter.

  • For POST requests, the "message" of the HMAC is the body of the request.
  • For GET requests (CRC), the "message" of the HMAC is the string crc_token=<crc_token_query_parameter>.

Use the following Go functions as examples:

const webhookSecret = `74e3f83b8b73f456ac0d731aecf73e8b39a6aceecd67c544eae124cd08cd26ad`;

func validateCRCRequest(r *http.Request) {
  token := r.URL.Query().Get("crc_token")
  hmacHash := hmac.New(sha256.New, []byte(webhookSecret))
  hmacHash.Write([]byte("crc_token=" + token))
  expected := "sha256=" + base64.StdEncoding.EncodeToString(hmacHash.Sum(nil))
  if s := r.Header.Get("X-Webhook-Signature"); s != expected {
    panic("invalid webhook signature")
  }
}

func validateWebhookPayload(r *http.Request) {
  body, _ := io.ReadAll(r.Body)
  r.Body.Close()
  hmacHash := hmac.New(sha256.New, []byte(webhookSecret))
  hmacHash.Write(body)
  expected := "sha256=" + base64.StdEncoding.EncodeToString(hmacHash.Sum(nil))
  if s := r.Header.Get("X-Webhook-Signature"); s != expected {
    panic("invalid webhook signature")
  }
}

Manual CRC Invocation

The webhooksCrc endpoint allows manual invocation of the CRC on any webhook. This can be useful to instantly test CRC handler without having to wait 1 hour for automatic check.

Event Types and Payloads

Event TypePayload
brand.created
interface Payload {
  shop_id string;
  payload string;
}
brand.deleted
interface Payload {
  shop_id string;
  payload string;
}
brand.modified
interface Payload {
  shop_id string;
  payload string;
}
buy_link.created
interface Payload {
  shop_id string;
  payload string;
}
buy_link.deleted
interface Payload {
  shop_id string;
  payload string;
}
buy_link.modified
interface Payload {
  shop_id string;
  payload string;
}
category.created
interface Payload {
  shop_id string;
  payload string;
}
category.deleted
interface Payload {
  shop_id string;
  payload string;
}
category.modified
interface Payload {
  shop_id string;
  payload string;
}
content_block.created
interface Payload {
  shop_id string;
  payload string;
}
content_block.deleted
interface Payload {
  shop_id string;
  payload string;
}
content_block.modified
interface Payload {
  shop_id string;
  payload string;
}
customer.created
interface Payload {
  shop_id string;
  payload string;
}
customer.deleted
interface Payload {
  shop_id string;
  payload string;
}
customer.modified
interface Payload {
  shop_id string;
  payload string;
}
customer_balance.transaction
interface Payload {
  shop_id string;
  payload string;
}
customer_group.created
interface Payload {
  shop_id string;
  payload string;
}
customer_group.deleted
interface Payload {
  shop_id string;
  payload string;
}
customer_group.modified
interface Payload {
  shop_id string;
  payload string;
}
customer_inventory_note.created
interface Payload {
  shop_id string;
  payload string;
}
customer_inventory_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
customer_inventory_note.modified
interface Payload {
  shop_id string;
  payload string;
}
customer_note.created
interface Payload {
  shop_id string;
  payload string;
}
customer_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
customer_note.modified
interface Payload {
  shop_id string;
  payload string;
}
data_feed.created
interface Payload {
  shop_id string;
  payload string;
}
data_feed.deleted
interface Payload {
  shop_id string;
  payload string;
}
data_feed.modified
interface Payload {
  shop_id string;
  payload string;
}
domain.created
interface Payload {
  shop_id string;
  payload string;
}
domain.deleted
interface Payload {
  shop_id string;
  payload string;
}
file.created
interface Payload {
  shop_id string;
  payload string;
}
file.deleted
interface Payload {
  shop_id string;
  payload string;
}
fulfillment.created
interface Payload {
  shop_id string;
  payload string;
}
fulfillment.modified
interface Payload {
  shop_id string;
  payload string;
}
gateway.created
interface Payload {
  shop_id string;
  payload string;
}
gateway.deleted
interface Payload {
  shop_id string;
  payload string;
}
gateway.modified
interface Payload {
  shop_id string;
  payload string;
}
inventory_location.created
interface Payload {
  shop_id string;
  payload string;
}
inventory_location.modified
interface Payload {
  shop_id string;
  payload string;
}
inventory_location.relocate
interface Payload {
  shop_id string;
  payload string;
}
inventory_rule.created
interface Payload {
  shop_id string;
  payload string;
}
inventory_rule.deleted
interface Payload {
  shop_id string;
  payload string;
}
inventory_rule.modified
interface Payload {
  shop_id string;
  payload string;
}
inventory_rules_graph.modified
interface Payload {
  shop_id string;
  payload string;
}
key.added
interface Payload {
  shop_id string;
  payload string;
}
key.deleted
interface Payload {
  shop_id string;
  payload string;
}
menu.created
interface Payload {
  shop_id string;
  payload string;
}
menu.deleted
interface Payload {
  shop_id string;
  payload string;
}
menu.modified
interface Payload {
  shop_id string;
  payload string;
}
option.created
interface Payload {
  shop_id string;
  payload string;
}
option.deleted
interface Payload {
  shop_id string;
  payload string;
}
option.modified
interface Payload {
  shop_id string;
  payload string;
}
option_set.created
interface Payload {
  shop_id string;
  payload string;
}
option_set.deleted
interface Payload {
  shop_id string;
  payload string;
}
option_set.modified
interface Payload {
  shop_id string;
  payload string;
}
order.abandoned
interface Payload {
  shop_id string;
  payload string;
}
order.canceled
interface Payload {
  shop_id string;
  payload string;
}
order.captured
interface Payload {
  shop_id string;
  payload string;
}
order.deleted
interface Payload {
  shop_id string;
  payload string;
}
order.imported
interface Payload {
  shop_id string;
  payload string;
}
order.modified
interface Payload {
  shop_id string;
  payload string;
}
order.paid
interface Payload {
  shop_id string;
  payload string;
}
order.refunded
interface Payload {
  shop_id string;
  payload string;
}
order.reserved
interface Payload {
  shop_id string;
  payload string;
}
order.restocked
interface Payload {
  shop_id string;
  payload string;
}
order.unabandoned
interface Payload {
  shop_id string;
  payload string;
}
order.unreserved
interface Payload {
  shop_id string;
  payload string;
}
order_note.created
interface Payload {
  shop_id string;
  payload string;
}
order_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
order_note.modified
interface Payload {
  shop_id string;
  payload string;
}
order_risk.created
interface Payload {
  shop_id string;
  payload string;
}
order_risk.deleted
interface Payload {
  shop_id string;
  payload string;
}
packaging.created
interface Payload {
  shop_id string;
  payload string;
}
packaging.deleted
interface Payload {
  shop_id string;
  payload string;
}
packaging.modified
interface Payload {
  shop_id string;
  payload string;
}
price_list.created
interface Payload {
  shop_id string;
  payload string;
}
price_list.deleted
interface Payload {
  shop_id string;
  payload string;
}
price_list.modified
interface Payload {
  shop_id string;
  payload string;
}
product.created
interface Payload {
  shop_id string;
  payload string;
}
product.deleted
interface Payload {
  shop_id string;
  payload string;
}
product.modified
interface Payload {
  shop_id string;
  payload string;
}
product_field_set.created
interface Payload {
  shop_id string;
  payload string;
}
product_field_set.deleted
interface Payload {
  shop_id string;
  payload string;
}
product_field_set.modified
interface Payload {
  shop_id string;
  payload string;
}
promotion.created
interface Payload {
  shop_id string;
  payload string;
}
promotion.deleted
interface Payload {
  shop_id string;
  payload string;
}
promotion.modified
interface Payload {
  shop_id string;
  payload string;
}
purchase_order.created
interface Payload {
  shop_id string;
  payload string;
}
purchase_order.deleted
interface Payload {
  shop_id string;
  payload string;
}
purchase_order.modified
interface Payload {
  shop_id string;
  payload string;
}
purchase_order.received
interface Payload {
  shop_id string;
  payload string;
}
purchase_order.rejected
interface Payload {
  shop_id string;
  payload string;
}
purchase_order_note.created
interface Payload {
  shop_id string;
  payload string;
}
purchase_order_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
purchase_order_note.modified
interface Payload {
  shop_id string;
  payload string;
}
reservation.created
interface Payload {
  shop_id string;
  payload string;
}
reservation.deleted
interface Payload {
  shop_id string;
  payload string;
}
reservation.modified
interface Payload {
  shop_id string;
  payload string;
}
return.approved
interface Payload {
  shop_id string;
  payload string;
}
return.created
interface Payload {
  shop_id string;
  payload string;
}
return.deleted
interface Payload {
  shop_id string;
  payload string;
}
return.modified
interface Payload {
  shop_id string;
  payload string;
}
return.rejected
interface Payload {
  shop_id string;
  payload string;
}
return_note.created
interface Payload {
  shop_id string;
  payload string;
}
return_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
return_note.modified
interface Payload {
  shop_id string;
  payload string;
}
return_policy.created
interface Payload {
  shop_id string;
  payload string;
}
return_policy.deleted
interface Payload {
  shop_id string;
  payload string;
}
return_policy.modified
interface Payload {
  shop_id string;
  payload string;
}
review_dimension.created
interface Payload {
  shop_id string;
  payload string;
}
review_dimension.deleted
interface Payload {
  shop_id string;
  payload string;
}
review_dimension.modified
interface Payload {
  shop_id string;
  payload string;
}
review_dimension_set.created
interface Payload {
  shop_id string;
  payload string;
}
review_dimension_set.deleted
interface Payload {
  shop_id string;
  payload string;
}
review_dimension_set.modified
interface Payload {
  shop_id string;
  payload string;
}
sales_channel.created
interface Payload {
  shop_id string;
  payload string;
}
sales_channel.deleted
interface Payload {
  shop_id string;
  payload string;
}
sales_channel.modified
interface Payload {
  shop_id string;
  payload string;
}
scheduled_fulfillment.created
interface Payload {
  shop_id string;
  payload string;
}
scheduled_fulfillment.deleted
interface Payload {
  shop_id string;
  payload string;
}
scheduled_fulfillment.modified
interface Payload {
  shop_id string;
  payload string;
}
shipping_provider.created
interface Payload {
  shop_id string;
  payload string;
}
shipping_provider.deleted
interface Payload {
  shop_id string;
  payload string;
}
shipping_provider.modified
interface Payload {
  shop_id string;
  payload string;
}
shipping_rule.created
interface Payload {
  shop_id string;
  payload string;
}
shipping_rule.deleted
interface Payload {
  shop_id string;
  payload string;
}
shipping_rule.modified
interface Payload {
  shop_id string;
  payload string;
}
shipping_rules_graph.modified
interface Payload {
  shop_id string;
  payload string;
}
shop.deleted
interface Payload {
  shop_id string;
  payload string;
}
shop.modified
interface Payload {
  shop_id string;
  payload string;
}
shop_settings.modified
interface Payload {
  shop_id string;
  payload string;
}
subscription.canceled
interface Payload {
  shop_id string;
  payload string;
}
subscription.created
interface Payload {
  shop_id string;
  payload string;
}
subscription.failed
interface Payload {
  shop_id string;
  payload string;
}
subscription.pending_payment
interface Payload {
  shop_id string;
  payload string;
}
subscription.upcoming_payment
interface Payload {
  shop_id string;
  payload string;
}
supplier.created
interface Payload {
  shop_id string;
  payload string;
}
supplier.deleted
interface Payload {
  shop_id string;
  payload string;
}
supplier.modified
interface Payload {
  shop_id string;
  payload string;
}
survey_question.created
interface Payload {
  shop_id string;
  payload string;
}
survey_question.deleted
interface Payload {
  shop_id string;
  payload string;
}
survey_question.modified
interface Payload {
  shop_id string;
  payload string;
}
tax_rule.created
interface Payload {
  shop_id string;
  payload string;
}
tax_rule.deleted
interface Payload {
  shop_id string;
  payload string;
}
tax_rule.modified
interface Payload {
  shop_id string;
  payload string;
}
tax_rules_graph.modified
interface Payload {
  shop_id string;
  payload string;
}
transfer.created
interface Payload {
  shop_id string;
  payload string;
}
transfer.deleted
interface Payload {
  shop_id string;
  payload string;
}
transfer.modified
interface Payload {
  shop_id string;
  payload string;
}
transfer.received
interface Payload {
  shop_id string;
  payload string;
}
transfer.rejected
interface Payload {
  shop_id string;
  payload string;
}
transfer_note.created
interface Payload {
  shop_id string;
  payload string;
}
transfer_note.deleted
interface Payload {
  shop_id string;
  payload string;
}
transfer_note.modified
interface Payload {
  shop_id string;
  payload string;
}
variant.created
interface Payload {
  shop_id string;
  payload string;
}
variant.deleted
interface Payload {
  shop_id string;
  payload string;
}
variant.modified
interface Payload {
  shop_id string;
  payload string;
}
PREVIOUS
Typical Bulk Get Endpoint
NEXT
API Overview