Developers

    Build on Simple Inventory

    A REST API for your items and stock, signed webhooks the moment something changes, and a Zapier app for everything in between. Included in every plan.

    Get started
    Request
    curl "https://app.simpleinventory.io/api/v1/items?limit=1" \
      -H "Authorization: Bearer si_live_…"
    Response · 200 OK
    {
      "data": [
        { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
      ],
      "pagination": {
        "limit": 1,
        "offset": 0,
        "hasMore": true
      }
    }
    Base URL
    https://app.simpleinventory.io/api/v1
    Authentication
    Bearer API key
    Rate limit
    120 requests/min per key
    Webhooks
    Signed with HMAC-SHA256
    On this page

    Start here

    Getting started

    The API works on one workspace at a time. A key belongs to the workspace it was created in, and every request reads and writes that workspace's inventory. Requests and responses are JSON over HTTPS, and every path below is relative to https://app.simpleinventory.io/api/v1.

    Create an API key

    1. 1
      Sign in as a workspace owner or admin and open Settings → Integrations → API Keys.
    2. 2
      Create a key. Give it a name you'll recognise later (for example "Shop sync") and choose its permissions.
    3. 3
      Copy the key straight away — it starts with si_live_ and is shown only once. We keep only a fingerprint of it, so it can't be shown again. If you lose it, revoke it and create a new one.

    Treat a key like a password. Keep it on your server, never in a web page or mobile app. You can revoke a key from the same settings page at any time; requests that use it are refused from then on.

    Authentication

    Send the key in the Authorization header of every request. Send Content-Type: application/json with any request that has a body.

    Request
    curl https://app.simpleinventory.io/api/v1/me \
      -H "Authorization: Bearer si_live_…"

    Permissions

    Each key has one or both of two permissions, chosen when you create it. Every endpoint below says which one it needs; a request without it gets 403 forbidden.

    read
    permission
    Look up items, folders and recent events, and subscribe to events (what Zapier triggers use).
    write
    permission
    Create and edit items and change stock.

    Errors

    Errors use standard HTTP status codes and a JSON body with a machine-readable error code and a readable message. Branch on the code, not the message — messages may be reworded.

    Response · 404 Not Found
    {
      "error": "not_found",
      "message": "Item not found"
    }
    400 validation_error
    Bad request
    A field or query parameter is missing or invalid. May include a details list.
    401 unauthorized
    No valid key
    The key is missing, malformed, revoked or expired.
    403 forbidden
    Wrong permission
    The key doesn't have the permission this endpoint needs.
    402 subscription_required
    No subscription
    Creating and editing items needs an active subscription or trial.
    403 item_limit_reached
    Plan limit
    Your plan's item limit has been reached.
    404 not_found
    Not found
    There's no such item in this workspace. folder_not_found is the same for a folder.
    409 duplicate_sku
    Conflict
    Another item already uses that SKU.
    409 item_archived
    Conflict
    The item is archived and can't be changed.
    409 variant_parent_not_stockable
    Conflict
    You tried to change stock on a variant group. Change one of its variations.
    429 rate_limited
    Too many requests
    You're over the rate limit.
    500 internal_error
    Server error
    Something went wrong on our side. Retry after a short wait.

    Rate limits

    Each API key can make 120 requests per minute. Over that, you get 429 Too Many Requests with a Retry-After header saying how many seconds to wait. To change stock on many items, use bulk update (up to 100 items per request) rather than one request per item.

    Every endpoint that returns an item, and every webhook event, uses this same object. Money values are numbers, not strings. New fields may be added over time, so ignore any you don't recognise.

    An item with variations — a T-shirt in three sizes, say — is a variant group plus one item per variation. The variations hold the stock; the group's quantity is their total.

    Item
    {
      "id": 123,
      "name": "Blue Mug",
      "sku": "MUG-BLU",
      "quantity": 14,
      "unitCost": 4.5,
      "salePrice": 12,
      "lowStockThreshold": 5,
      "description": "12 oz ceramic mug",
      "barcode": "0123456789012",
      "folderId": 7,
      "folderName": "Kitchen",
      "parentItemId": null,
      "variantValues": null,
      "isVariantGroup": false,
      "archived": false,
      "createdAt": "2026-10-01T14:03:11.000Z",
      "updatedAt": "2026-10-12T09:41:27.000Z",
      "url": "https://app.simpleinventory.io/item/123"
    }
    id
    integer
    The item's unique ID.
    name
    string
    The item's name.
    sku
    string | null
    Your stock-keeping unit.
    quantity
    integer
    Stock on hand. For a variant group, the total of its variations.
    unitCost
    number | null
    What one unit costs you.
    salePrice
    number | null
    What you sell one unit for.
    lowStockThreshold
    integer | null
    The stock level that counts as low.
    description
    string | null
    Free-text description.
    barcode
    string | null
    Stored barcode value.
    folderId
    integer | null
    The folder the item is in.
    folderName
    string | null
    That folder's name.
    parentItemId
    integer | null
    On a variation: the ID of its variant group.
    variantValues
    object | null
    On a variation: which options it is, e.g. {"Size": "M"}.
    isVariantGroup
    boolean
    True for a variant group. It holds no stock of its own.
    archived
    boolean
    True once the item has been archived.
    createdAt
    string
    When the item was created (ISO 8601, UTC).
    updatedAt
    string
    When the item last changed (ISO 8601, UTC).
    url
    string
    Link to the item in Simple Inventory.

    Reference

    Endpoints

    All paths are relative to https://app.simpleinventory.io/api/v1. Where a path takes an {identifier}, pass the item's numeric ID or its SKU.

    Check a key

    GET

    /me

    Any key

    Returns the workspace and API key the request is authenticated as. The quickest way to test a key.

    Request
    curl https://app.simpleinventory.io/api/v1/me \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY"
    Response · 200 OK
    {
      "data": {
        "organization": {
          "id": 12,
          "name": "Acme Supply"
        },
        "apiKey": {
          "id": 5,
          "name": "Shop sync",
          "scopes": [
            "read",
            "write"
          ]
        }
      }
    }

    List items

    GET

    /items

    Needs read

    Lists the items in your workspace that are not archived.

    Query parameters

    limit
    integer
    How many items to return, 1–500. Default 100.
    offset
    integer
    How many items to skip. Default 0.
    include
    string
    leaves (default): items that hold stock — standalone items and variations. groups: what you see in the app — standalone items and variant groups. all: both.
    sku
    string
    Only the item with exactly this SKU. Not case-sensitive.
    search
    string
    Items whose name or SKU contains this text. Not case-sensitive.

    To page through everything, add limit to offset after each request until hasMore is false.

    Request
    curl "https://app.simpleinventory.io/api/v1/items?search=mug&limit=50" \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY"
    Response · 200 OK
    {
      "data": [
        {
          "id": 123,
          "name": "Blue Mug",
          "sku": "MUG-BLU",
          "quantity": 14,
          "unitCost": 4.5,
          "salePrice": 12,
          "lowStockThreshold": 5,
          "description": "12 oz ceramic mug",
          "barcode": "0123456789012",
          "folderId": 7,
          "folderName": "Kitchen",
          "parentItemId": null,
          "variantValues": null,
          "isVariantGroup": false,
          "archived": false,
          "createdAt": "2026-10-01T14:03:11.000Z",
          "updatedAt": "2026-10-12T09:41:27.000Z",
          "url": "https://app.simpleinventory.io/item/123"
        }
      ],
      "pagination": {
        "limit": 50,
        "offset": 0,
        "hasMore": false
      }
    }

    Get an item

    GET

    /items/{identifier}

    Needs read

    Fetches one item by its numeric ID or by its SKU.

    A number is tried as an ID first, then as a SKU if no item has that ID. SKUs match exactly, or ignoring case when only one item matches that way. If a numeric SKU could also be another item's ID, use GET /items?sku= instead. URL-encode SKUs that contain spaces or symbols. A variant group also comes back with variants: its variations, as item objects.

    Request
    curl https://app.simpleinventory.io/api/v1/items/MUG-BLU \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY"
    Response · 200 OK
    {
      "data": {
        "id": 123,
        "name": "Blue Mug",
        "sku": "MUG-BLU",
        "quantity": 14,
        "unitCost": 4.5,
        "salePrice": 12,
        "lowStockThreshold": 5,
        "description": "12 oz ceramic mug",
        "barcode": "0123456789012",
        "folderId": 7,
        "folderName": "Kitchen",
        "parentItemId": null,
        "variantValues": null,
        "isVariantGroup": false,
        "archived": false,
        "createdAt": "2026-10-01T14:03:11.000Z",
        "updatedAt": "2026-10-12T09:41:27.000Z",
        "url": "https://app.simpleinventory.io/item/123"
      }
    }

    Create an item

    POST

    /items

    Needs write

    Creates an item, just as adding it in the app would: it appears in the item's history and counts toward your plan's item limit.

    JSON body

    name
    stringrequired
    The item's name.
    sku
    string
    Must not already be used by another item in your workspace.
    quantity
    integer
    Opening stock. Default 0.
    unitCost
    number
    What one unit costs you.
    salePrice
    number
    What you sell one unit for.
    lowStockThreshold
    integer
    Low stock alerts fire when stock drops to this level.
    description
    string
    Free-text description.
    barcode
    string
    A barcode value to store with the item.
    folderId
    integer
    The folder to put it in. Get IDs from GET /folders.

    Returns 409 duplicate_sku if the SKU is taken, 404 folder_not_found if the folder is not in your workspace, 403 item_limit_reached at your plan's item limit, and 402 subscription_required if the workspace has no active subscription.

    Request
    curl -X POST https://app.simpleinventory.io/api/v1/items \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name": "Blue Mug", "sku": "MUG-BLU", "quantity": 14, "salePrice": 12}'
    Response · 201 Created
    {
      "data": {
        "id": 123,
        "name": "Blue Mug",
        "sku": "MUG-BLU",
        "quantity": 14,
        "unitCost": 4.5,
        "salePrice": 12,
        "lowStockThreshold": 5,
        "description": "12 oz ceramic mug",
        "barcode": "0123456789012",
        "folderId": 7,
        "folderName": "Kitchen",
        "parentItemId": null,
        "variantValues": null,
        "isVariantGroup": false,
        "archived": false,
        "createdAt": "2026-10-01T14:03:11.000Z",
        "updatedAt": "2026-10-12T09:41:27.000Z",
        "url": "https://app.simpleinventory.io/item/123"
      }
    }

    Update an item

    PATCH

    /items/{identifier}

    Needs write

    Changes an item's details. Send only the fields you want to change; send null to clear an optional field.

    JSON body

    name
    string
    New name.
    sku
    string
    New SKU. Must not be used by another item.
    unitCost
    number
    New unit cost.
    salePrice
    number
    New sale price.
    lowStockThreshold
    integer
    New low stock level. Alerts are re-checked straight away.
    description
    string
    New description.
    barcode
    string
    New barcode value.
    folderId
    integer
    Move the item to this folder.

    Stock can't be changed here: a quantity field returns 400. Use change stock so the change is recorded in the item's history. Archived items can't be edited (409 item_archived), and the same SKU, folder and subscription errors as creating an item apply.

    Request
    curl -X PATCH https://app.simpleinventory.io/api/v1/items/123 \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"salePrice": 12.5, "lowStockThreshold": 6}'
    Response · 200 OK
    {
      "data": {
        "id": 123,
        "name": "Blue Mug",
        "sku": "MUG-BLU",
        "quantity": 14,
        "unitCost": 4.5,
        "salePrice": 12.5,
        "lowStockThreshold": 6,
        "description": "12 oz ceramic mug",
        "barcode": "0123456789012",
        "folderId": 7,
        "folderName": "Kitchen",
        "parentItemId": null,
        "variantValues": null,
        "isVariantGroup": false,
        "archived": false,
        "createdAt": "2026-10-01T14:03:11.000Z",
        "updatedAt": "2026-10-12T09:41:27.000Z",
        "url": "https://app.simpleinventory.io/item/123"
      }
    }

    Change stock

    Sets, adds to or removes from an item's stock. The change is recorded in the item's history, checked against its low stock level, and sent on to WooCommerce if you have it connected.

    JSON body

    quantity
    integer ≥ 0required
    The amount.
    operation
    string
    set (default): make stock exactly quantity. increment: add quantity. decrement: remove quantity — stock never goes below 0.
    reason
    string
    Saved as the note on the history entry.

    A variant group holds no stock of its own, so changing one returns 409 variant_parent_not_stockable; change one of its variations instead. An archived item returns 409 item_archived.

    Request
    curl -X PATCH https://app.simpleinventory.io/api/v1/items/MUG-BLU/quantity \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"operation": "decrement", "quantity": 3, "reason": "Order #1042"}'
    Response · 200 OK
    {
      "data": {
        "id": 123,
        "name": "Blue Mug",
        "sku": "MUG-BLU",
        "quantity": 14,
        "unitCost": 4.5,
        "salePrice": 12,
        "lowStockThreshold": 5,
        "description": "12 oz ceramic mug",
        "barcode": "0123456789012",
        "folderId": 7,
        "folderName": "Kitchen",
        "parentItemId": null,
        "variantValues": null,
        "isVariantGroup": false,
        "archived": false,
        "createdAt": "2026-10-01T14:03:11.000Z",
        "updatedAt": "2026-10-12T09:41:27.000Z",
        "url": "https://app.simpleinventory.io/item/123"
      },
      "previousQuantity": 17,
      "newQuantity": 14
    }

    Bulk stock update

    POST

    /items/bulk-update

    Needs write

    Changes stock on up to 100 items in one request. Each item is applied on its own, so one failure doesn't stop the rest.

    JSON body

    items
    arrayrequired
    1–100 changes. Each one takes the fields below.
    items[].id / items[].sku
    integer / stringrequired
    Which item. Give one or the other.
    items[].quantity
    integer ≥ 0required
    The amount.
    items[].operation
    string
    set (default), increment or decrement.
    items[].reason
    string
    Saved as the note on the history entry.

    success is true only when every change was applied. Anything that failed is listed in errors with the reason.

    Request
    curl -X POST https://app.simpleinventory.io/api/v1/items/bulk-update \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"items": [
        {"sku": "MUG-BLU", "operation": "decrement", "quantity": 3},
        {"sku": "MUG-RED", "quantity": 24},
        {"sku": "MUG-GRN", "quantity": 5}
      ]}'
    Response · 200 OK
    {
      "success": false,
      "processed": 2,
      "failed": 1,
      "results": [
        {
          "id": 123,
          "sku": "MUG-BLU",
          "previousQuantity": 17,
          "newQuantity": 14
        },
        {
          "id": 124,
          "sku": "MUG-RED",
          "previousQuantity": 0,
          "newQuantity": 24
        }
      ],
      "errors": [
        {
          "item": {
            "sku": "MUG-GRN",
            "quantity": 5,
            "operation": "set"
          },
          "error": "Item not found"
        }
      ]
    }

    List folders

    GET

    /folders

    Needs read

    Lists your workspace's folders, each with its full path.

    Request
    curl https://app.simpleinventory.io/api/v1/folders \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY"
    Response · 200 OK
    {
      "data": [
        {
          "id": 3,
          "name": "Warehouse",
          "parentId": null,
          "path": "Warehouse"
        },
        {
          "id": 7,
          "name": "Shelf A",
          "parentId": 3,
          "path": "Warehouse / Shelf A"
        }
      ]
    }

    Recent events

    GET

    /events

    Needs read

    Returns the most recent events of one type, newest first, in the same shape as a webhook's data. Handy for testing your webhook handler with real data.

    Query parameters

    type
    stringrequired
    One of the event names, e.g. inventory.updated.
    limit
    integer
    How many events, 1–25. Default 10.
    Request
    curl "https://app.simpleinventory.io/api/v1/events?type=inventory.updated&limit=1" \
      -H "Authorization: Bearer $SIMPLE_INVENTORY_API_KEY"
    Response · 200 OK
    {
      "data": [
        {
          "id": 98231,
          "occurredAt": "2026-10-12T09:41:27.000Z",
          "type": "out",
          "quantityChange": -3,
          "previousQuantity": 17,
          "newQuantity": 14,
          "notes": "Order #1042",
          "source": "api",
          "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
        }
      ]
    }

    Hooks (Zapier)

    POST

    /hooks

    DELETE/hooks/{id}

    Used by the Zapier app; REST-hook subscribe/unsubscribe. To receive events yourself, add a webhook in Settings instead.

    Real-time

    Webhooks

    A webhook sends an HTTP POST to your URL whenever something changes in your inventory — no matter where the change was made: the web app, the phone app, the API, Zapier or WooCommerce.

    Add a webhook

    1. 1
      Sign in as a workspace owner or admin and open Settings → Integrations → Webhooks.
    2. 2
      Add a webhook: give it a name, the public URL that should receive events (use HTTPS), and the events to send. Choose none to receive all of them.
    3. 3
      Copy the signing secret. Like an API key, it's shown once. You can regenerate it later; the old secret stops working straight away.
    4. 4
      Send a test from the webhook's settings. It delivers an event named test, so you can check your endpoint answers before real events arrive.

    Pause a webhook to stop deliveries without deleting it. Deliveries that were still waiting when you paused it are cancelled, not saved up for later.

    Events

    inventory.updated
    Stock changed. Stock was added, removed or counted.
    inventory.low_stock
    Low stock. An item dropped to its low stock level.
    inventory.out_of_stock
    Out of stock. An item ran out.
    item.created
    Item created. A new item was added.
    item.updated
    Item updated. An item's name, SKU, price, folder or other details changed.
    item.archived
    Item archived. An item was archived.

    inventory.low_stock and inventory.out_of_stock follow the app's own stock alerts. They fire once when an item drops to its low stock level or runs out — not again on every movement while it stays low — and they respect each item's alert settings. Items that have never had any stock don't trigger them.

    Delivery format

    Each delivery is a JSON body with these headers:

    Content-Type
    header
    application/json
    User-Agent
    header
    SimpleInventory-Webhook/1.0
    X-SI-Event
    header
    The event name, e.g. inventory.updated.
    X-SI-Delivery-Id
    header
    The delivery's ID — the same as id in the body, and the same on every retry.
    X-SI-Timestamp
    header
    When this attempt was sent, in Unix seconds.
    X-SI-Signature
    header
    v1= followed by the signature. See verify signatures.
    Body
    {
      "id": "7d1f0c3e-2b4a-4f8e-9c61-5a0e3b9d2f47",
      "event": "inventory.updated",
      "createdAt": "2026-10-12T09:41:28.512Z",
      "organizationId": 12,
      "data": {
        "id": 98231,
        "occurredAt": "2026-10-12T09:41:27.000Z",
        "type": "out",
        "quantityChange": -3,
        "previousQuantity": 17,
        "newQuantity": 14,
        "notes": "Order #1042",
        "source": "api",
        "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
      }
    }
    id
    string
    The delivery's ID. Use it to skip deliveries you've already handled.
    event
    string
    The event name.
    createdAt
    string
    When the delivery was created (ISO 8601, UTC).
    organizationId
    integer
    Your workspace's ID.
    data
    object
    The event. Its shape depends on the event; see event payloads.

    Reply with any 2xx status to acknowledge a delivery. Reply quickly and do slow work afterwards: a timeout counts as a failure and is retried. The same delivery can arrive more than once, and deliveries are not guaranteed to arrive in order — use X-SI-Delivery-Id to skip repeats and occurredAt to order events.

    Event payloads

    Every event's data has an id, an occurredAt time (ISO 8601, UTC) and the item it is about, as a full item object (shortened in the examples below). Some events add more fields.

    inventory.updated

    Stock was added, removed or counted.

    id
    integer
    The ID of the stock change in the item's history.
    type
    string
    "in", "out" or "adjust".
    quantityChange
    integer
    New quantity minus previous quantity. Negative when stock went down.
    previousQuantity
    integer
    Stock before the change.
    newQuantity
    integer
    Stock after the change.
    notes
    string | null
    The note recorded with the change, if any.
    source
    string | null
    Where the change was made: "web", "mobile", "api", "zapier", "woocommerce_webhook", or null when unknown.
    data
    {
      "id": 98231,
      "occurredAt": "2026-10-12T09:41:27.000Z",
      "type": "out",
      "quantityChange": -3,
      "previousQuantity": 17,
      "newQuantity": 14,
      "notes": "Order #1042",
      "source": "api",
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    inventory.low_stock

    An item dropped to its low stock level.

    id
    integer
    The ID of the stock alert.
    quantity
    integer
    Stock when the alert fired.
    lowStockThreshold
    integer
    The item's low stock level.
    data
    {
      "id": 4410,
      "occurredAt": "2026-10-12T09:41:27.000Z",
      "quantity": 4,
      "lowStockThreshold": 5,
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    inventory.out_of_stock

    An item ran out.

    id
    integer
    The ID of the stock alert.
    quantity
    integer
    Always 0.
    data
    {
      "id": 4411,
      "occurredAt": "2026-10-12T10:02:55.000Z",
      "quantity": 0,
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    item.created

    A new item was added.

    id
    integer
    The new item's ID.
    data
    {
      "id": 123,
      "occurredAt": "2026-10-01T14:03:11.000Z",
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    item.updated

    An item's name, SKU, price, folder or other details changed.

    id
    string
    A string made of the item ID and the time of the change, e.g. "123-1760262087000".
    changedFields
    string[]
    Which fields of the item object changed, e.g. ["name", "salePrice"]. Stock changes are sent as inventory.updated instead.
    data
    {
      "id": "123-1760262087000",
      "occurredAt": "2026-10-12T09:41:27.000Z",
      "changedFields": [
        "name",
        "salePrice"
      ],
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    item.archived

    An item was archived.

    id
    integer
    The ID of the archive entry in the item's history.
    data
    {
      "id": 98240,
      "occurredAt": "2026-10-12T11:15:03.000Z",
      "item": { "id": 123, "name": "Blue Mug", "sku": "MUG-BLU", … }
    }

    Verify signatures

    Every delivery is signed with your webhook's secret, so you can check it came from Simple Inventory and wasn't altered. The signature is a hex HMAC-SHA256 of the X-SI-Timestamp value, a full stop, and the raw request body:

    Signature
    X-SI-Signature: v1=hex( HMAC_SHA256( secret, "<X-SI-Timestamp>.<raw body>" ) )
    1. 1
      Read the raw body bytes, before any JSON parsing.
    2. 2
      Reject the request if X-SI-Timestamp is more than 5 minutes from now. This stops an old request being replayed.
    3. 3
      Compute the expected signature and compare it with a constant-time comparison.
    Node.js (Express)
    import crypto from "node:crypto";
    import express from "express";
    
    const app = express();
    // The signing secret shown when you created the webhook.
    const SECRET = process.env.SIMPLE_INVENTORY_WEBHOOK_SECRET;
    
    // express.raw keeps the exact bytes we signed. Parsing the JSON and
    // re-serialising it would change them and break the signature.
    app.post("/simple-inventory", express.raw({ type: "application/json" }), (req, res) => {
      const timestamp = req.get("X-SI-Timestamp") ?? "";
      const signature = req.get("X-SI-Signature") ?? "";
      const rawBody = req.body.toString("utf8");
    
      // Reject anything more than 5 minutes old, so a captured request
      // can't be replayed later.
      const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
      if (!timestamp || !(ageSeconds <= 300)) {
        return res.status(400).send("Stale or missing timestamp");
      }
    
      const expected =
        "v1=" +
        crypto
          .createHmac("sha256", SECRET)
          .update(`${timestamp}.${rawBody}`)
          .digest("hex");
    
      const given = Buffer.from(signature);
      const wanted = Buffer.from(expected);
      if (given.length !== wanted.length || !crypto.timingSafeEqual(given, wanted)) {
        return res.status(401).send("Invalid signature");
      }
    
      const delivery = JSON.parse(rawBody);
      // Retries reuse the same id: skip any delivery you have already handled.
      console.log(delivery.id, delivery.event);
    
      res.sendStatus(200); // Acknowledge first, then do any slow work.
    });
    
    app.listen(3000);

    Retries and failures

    2xx
    Delivered
    Done. A success also resets the webhook's failure count.
    Timeout, network error, 408, 429, 5xx
    Retried
    Tried again after 1 minute, 5 minutes, 30 minutes, 2 hours and 8 hours — 6 attempts in all over about 10½ hours. If the last attempt fails, the delivery is marked failed.
    410 Gone
    Stopped
    We stop sending to that URL: the webhook is switched off. A subscription made through the API or Zapier is removed instead.
    Any other 4xx
    Failed
    Marked failed straight away, with no retries.

    After 10 failed deliveries in a row, the webhook is switched off automatically. Fix your endpoint, then turn the webhook back on in Settings → Integrations → Webhooks.

    No code

    Zapier

    Connect Simple Inventory to thousands of other apps with Zapier, without writing any code. When Zapier asks you to connect your account, paste an API key: give it read for triggers and searches, and write as well if your Zaps create or change items.

    Triggers

    Start a Zap the moment something happens:

    • Stock Changed
    • Item Low on Stock
    • Item Out of Stock
    • New Item
    • Item Updated
    • Item Archived

    Actions and search

    Let other apps update your inventory:

    • Create Item
    • Update Item
    • Adjust Stock: add, remove or set an exact count
    • Find Item: search by SKU or name

    Already a customer? Your Zapier settings page has the link to our Zapier app and walks you through connecting it.

    Open Zapier settings

    Questions?

    Help

    Stuck, or need something the API doesn't do yet? Email support@simpleinventory.io or use our support form.