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; }
NOTEDo 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:
-
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.
-
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
-
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. -
The webhook handler must respond to this GET request within 3 seconds with a JSON body containing the
response_tokenfield. The value ofresponse_tokenshould be the HMAC SHA256 hash of thecrc_token, base64-encoded. Thecrc_tokenshould 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)) + `"}`)) } -
The CRC check is performed every hour.
-
If the webhook endpoint fails the CRC for 6 consecutive hours, the webhook will transition from the
okstate to thefailedstate. 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 Type | Payload |
|---|---|
| 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;
} |