Webhooks

Webhooks are great if you’re running an app that needs current, instantaneous information from a third-party application. They’re simple to set up and very easy to use.

Graceful retries

Once a webhook notification is received, you should acknowledge success by providing an HTTP 20X response within 10 seconds. If a response isn't delivered within this time, we will attempt to resend the notification three more times according to the following schedule:

  • 30 seconds or more after the initial attempt
  • 2 minutes or more after the second attempt
  • 15 minutes or more after the third attempt

With each attempt, you'll also be given an X-Katana-Retry-Num HTTP header indicating the attempt number (1, 2, or 3).

Using temporary endpoint URLs, you can quickly inspect webhook requests. You can create these URLs using a free hosted service such as https://webhook.site.

Events

You can configure the following events to trigger a message to registered webhooks:

EventDescription
sales_order.createdOccurs when a new sales order is created.
sales_order.updatedOccurs when a sales order is updated (any attribute).
sales_order.deletedOccurs when a sales order is deleted.
sales_order.packedOccurs when a new sales status is marked as PACKED.
sales_order.deliveredOccurs when a new sales status is marked as DELIVERED.
sales_order.availability_updatedOccurs when availability or expected date is updated for products or ingredients.
purchase_order.createdOccurs whenever a new purchase order is created.
purchase_order.updatedOccurs whenever a purchase order is updated.
purchase_order.deletedOccurs whenever a purchase order is deleted.
purchase_order.partially_receivedOccurs whenever the purchase order status is marked as PARTIALLY_RECEIVED.
purchase_order.receivedOccurs whenever the purchase order status is marked as RECEIVED.
purchase_order_row.createdOccurs whenever a new purchase order row is created.
purchase_order_row.updatedOccurs whenever a new purchase order row is updated.
purchase_order_row.deletedOccurs whenever a purchase order row is deleted.
purchase_order_row.receivedOccurs whenever the purchase order row is marked received.
manufacturing_order.createdOccurs whenever a new manufacturing order is created.
manufacturing_order.updatedOccurs whenever a manufacturing order is updated (except updates of ingredient_availability, total_planned_time, total_actual_time, and cost-related fields).
manufacturing_order.deletedOccurs whenever a manufacturing order is deleted.
manufacturing_order.in_progressOccurs whenever the manufacturing order status gets marked as IN_PROGRESS.
manufacturing_order.blockedOccurs whenever the manufacturing order status gets marked as BLOCKED.
manufacturing_order.doneOccurs whenever the manufacturing order status gets marked as DONE.
manufacturing_order_operation_row.createdOccurs whenever an operation row is added to a manufacturing order.
manufacturing_order_operation_row.updatedOccurs whenever an operation row is updated in a manufacturing order.
manufacturing_order_operation_row.deletedOccurs whenever an operation row is deleted from a manufacturing order.
manufacturing_order_operation_row.in_progressOccurs whenever the operation status gets marked as IN_PROGRESS.
manufacturing_order_operation_row.pausedOccurs whenever the operation status gets marked as PAUSED.
manufacturing_order_operation_row.blockedOccurs whenever the operation status gets marked as BLOCKED.
manufacturing_order_operation_row.completedOccurs whenever the operation status gets marked as COMPLETED.
manufacturing_order_recipe_row.createdOccurs whenever a recipe row is added to a manufacturing order.
manufacturing_order_recipe_row.updatedOccurs whenever a recipe row is updated in a manufacturing order.
manufacturing_order_recipe_row.deletedOccurs whenever a recipe row is deleted from a manufacturing order.
manufacturing_order_recipe_row.ingredients_in_stockOccurs whenever the ingredient availability of a manufacturing order recipe row is updated to IN_STOCK.
current_inventory.product_updatedOccurs whenever a product's current stock level or average cost is updated.
current_inventory.material_updatedOccurs whenever a material's current stock level or average cost is updated.
current_inventory.product_out_of_stockOccurs whenever a product's current stock level is below the optimal level (quantity_missing_or_excess<= 0).
current_inventory.material_out_of_stockOccurs whenever a material's current stock level is below the optimal level (quantity_missing_or_excess<= 0).
product.createdOccurs whenever a new product is created.
product.updatedOccurs whenever a product is updated.
product.deletedOccurs whenever a product is deleted.
material.createdOccurs whenever a new material is created.
material.updatedOccurs whenever a material is updated.
material.deletedOccurs whenever a material is deleted.
variant.createdOccurs whenever a new variant is added to a product or material.
variant.updatedOccurs whenever a variant is updated.
variant.deletedOccurs whenever a variant is deleted.
product_recipe_row.createdOccurs whenever a product recipe row is created.
product_recipe_row.updatedOccurs whenever a product recipe row is updated.
product_recipe_row.deletedOccurs whenever a product recipe row is deleted.
outsourced_purchase_order.createdOccurs whenever an outsourced purchase order is created.
outsourced_purchase_order.updatedOccurs whenever an outsourced purchase order is updated.
outsourced_purchase_order.deletedOccurs whenever an outsourced purchase order is deleted.
outsourced_purchase_order.receivedOccurs whenever an outsourced purchase order is received.
outsourced_purchase_order_row.createdOccurs whenever an outsourced purchase order row is created.
outsourced_purchase_order_row.updatedOccurs whenever an outsourced purchase order row is updated.
outsourced_purchase_order_row.deletedOccurs whenever an outsourced purchase order row is deleted.
outsourced_purchase_order_row.receivedOccurs whenever an outsourced purchase order row is received.
outsourced_purchase_order_recipe_row.createdOccurs whenever an outsourced purchase order recipe row is created.
outsourced_purchase_order_recipe_row.updatedOccurs whenever an outsourced purchase order recipe row is updated.
outsourced_purchase_order_recipe_row.deletedOccurs whenever an outsourced purchase order recipe row is deleted.

🚧

Since some events solve specific use cases(i.e. sales_order.packed and sales_order.delivered), if you're subscribed to both sales order updates and sales order status change to delivered, you may receive two identical webhook payloads since sales order status change to delivered is also considered an update.

Event object

AttributeDescription
resource_typestring
Indicates the resource affected by this event.
webhook_idnumber
Indicates registered webhook id that was used to send the payload.
actionstring
The event that triggered this webhook, e.g. sales_order.delivered.
objectobject
The object affected by this event. This will contain an id, status and an href to retrieve that resource. (href property doesn't apply to sales_order.deleted or product_recipe_row.deleted events)
Example event payload
HTTP/1.1 201 Created
Content-Type: application/json

{
  "resource_type": "sales_order",
  "action": "sales_order.delivered",
  "webhook_id": "<WEBHOOK_ID>",
  "object": {
  "id": "<SALES_ORDER_ID>",
  "status": "DELIVERED",
  "href": "https://api.katanamrp.com/v1/sales_orders/<SALES_ORDER_ID>"
  }
}

Verifying webhook signatures

To prevent attackers from imitating valid webhook events, you should verify request signatures on your server.

Each webhook you register will return a single secret token in the token field of the API response body used to generate an HMAC using SHA-256. You only need to register a webhook once.

With each webhook request, you'll be given an x-sha2-signature HTTP header indicating the calculated signature.

Our Developer Hub provides a detailed guide for manually verifying webhook signatures.