Developers

Feeds and webhooks for the systems around your warehouse

Pull any client's inventory as JSON or CSV, and get a signed HTTP POST the moment an invoice is paid, a request changes, or items arrive. No SDK to install, no OAuth dance.

Data feeds

One URL per client, read-only

Create a feed for a client in Settings → Integrations. The token is scoped to that client only, so a designer, gallery, or brand can wire it straight into their own tools without seeing anyone else's items.

  • JSON by default, CSV with one query parameter
  • Incremental pulls with updated_since
  • Locations, dimensions, values, tags, and photos
  • Revoke in one click; the URL dies instantly
GET https://stowley.com/api/public/feeds/<token>/inventory
GET https://stowley.com/api/public/feeds/<token>/inventory?format=csv
GET https://stowley.com/api/public/feeds/<token>/inventory?updated_since=2026-09-01T00:00:00Z

{
  "object": "inventory_feed",
  "generated_at": "2026-09-04T14:02:11.000Z",
  "client": { "id": "…", "name": "Hartley & Co." },
  "organization": { "name": "Northside Receiving" },
  "filter": { "updated_since": null },
  "count": 128,
  "items": [
    {
      "id": "…",
      "name": "Walnut sideboard",
      "sku": "HC-0042",
      "client_sku": "PO-118-3",
      "quantity": 1,
      "status": "in_storage",
      "condition": "excellent",
      "category": "furniture",
      "project": "Greenwich",
      "location": { "warehouse": "Main", "zone": "A", "aisle": "12", "bin": "3" },
      "dimensions_in": { "length": 72, "width": 18, "height": 34 },
      "weight_lbs": 140,
      "insured_value": 4200,
      "tags": ["fragile", "designer"],
      "notes": "Blanket wrap, no stacking",
      "image_count": 2,
      "primary_image_url": "https://…/sideboard.jpg",
      "storage_start_date": "2026-06-01",
      "created_at": "2026-06-01T12:00:00.000Z",
      "updated_at": "2026-08-20T09:30:00.000Z"
    }
  ]
}

Webhooks

Signed events, delivered within a minute

Subscribe an HTTPS endpoint to the events you care about. Every delivery carries an HMAC-SHA256 signature over the timestamp and body, so you can trust what arrives.

invoice.paidInvoice paid
request.createdService request created
request.status_changedService request status changed
request.completedService request completed
items.receivedItems received into inventory
POST https://hooks.example.com/stowley
Content-Type: application/json
X-Stowley-Event: invoice.paid
X-Stowley-Delivery: 5f1c…      (the event id; use it to de-duplicate)
X-Stowley-Signature: t=1756992131,v1=8a2f…

{
  "id": "5f1c…",
  "type": "invoice.paid",
  "created_at": "2026-09-04T14:02:11.000Z",
  "org_id": "…",
  "data": {
    "invoice": {
      "id": "…",
      "number": "INV-1042",
      "client_id": "…",
      "client_name": "Hartley & Co.",
      "total": 4820,
      "paid_at": "2026-09-04T14:02:09.000Z",
      "payment_method": "card",
      "stripe_payment_intent_id": "pi_…"
    }
  }
}

Verify the signature (Node)

Compute HMAC-SHA256 of <t>.<raw body> with your endpoint's secret and compare it to v1 in constant time. Reject anything older than five minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyStowleySignature(secret, header, rawBody, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  const got = Buffer.from(parts.v1 ?? "", "hex");
  const want = Buffer.from(expected, "hex");
  return got.length === want.length && timingSafeEqual(got, want);
}

FAQ

Questions integrators ask

Is there a full REST API?
Not yet. The read-only feeds cover the most common pull (a client's inventory), and webhooks cover the pushes. Both are versioned by shape: fields are only ever added, never renamed or removed. If you need a write API, tell us what you're building.
How are webhooks retried?
A delivery counts as successful on any 2xx response within 10 seconds. Anything else is retried with backoff at 1 minute, 5 minutes, 30 minutes, and 2 hours, five attempts in total. Each endpoint's last result shows in Settings, and you can replay a test event at any time.
Can I use this with Zapier or Make?
Yes. Point a Zapier 'Webhooks by Zapier' or Make 'Custom webhook' trigger at the URL they give you, subscribe it in Stowley, and every event arrives as JSON. That's the fastest route to QuickBooks, Slack, Google Sheets, and the rest.
Who can see a feed?
Anyone with the URL, which is why the token is long, random, and scoped to a single client. Revoking a feed in Settings stops it immediately. Feeds never include another client's items or any billing data.
Which plan includes this?
Feeds and webhooks are part of the Enterprise plan. Sandbox workspaces have them switched on so you can try the whole flow before talking to us.

Migrating from another system? See the import path.

Live demo

Try it in a sandbox first

Sandbox workspaces have feeds and webhooks switched on. Spin one up, add an endpoint, and send a test event.

Start a free trial

Quick email check-in for a demo. No strings attached.