Webhooks

Webhooks allow your application to receive real-time notifications when events happen in your storq.io account. Instead of polling the API, webhooks push data to your server when events occur.

Setting Up Webhooks

Configure webhooks in your dashboard under Settings → Webhooks. Provide a URL endpoint where storq.io will send event notifications.

Webhook Endpoint Requirements

Event Types

storq.io sends webhooks for the following events:

Product Events

Event Description
product.created A new product was created
product.updated A product was updated
product.deleted A product was deleted

Stock Events

Event Description
stock.received Stock was received into a location
stock.adjusted Stock quantity was adjusted
stock.transferred Stock was moved between locations
stock.low Stock level fell below threshold

Order Events

Event Description
order.created A new order was created
order.picked Order picking was completed
order.packed Order packing was completed
order.shipped Order was shipped with tracking
order.cancelled Order was cancelled

Webhook Payload

All webhook payloads follow this structure:

{
  "id": "evt_1234567890",
  "type": "order.shipped",
  "created": 1642435200,
  "data": {
    "object": {
      "id": 123,
      "customer_email": "[email protected]",
      "status": "shipped",
      "tracking_number": "1Z999AA10123456784",
      "shipped_at": "2026-01-16T14:20:00Z"
    }
  }
}

Verifying Webhook Signatures

Verify that webhooks are sent from storq.io by validating the signature in the Storq-Signature header.

require 'openssl'

def verify_webhook_signature(payload, signature, secret)
  expected = OpenSSL::HMAC.hexdigest(
    'SHA256',
    secret,
    payload
  )

  Rack::Utils.secure_compare(expected, signature)
end

payload = request.body.read
signature = request.headers['Storq-Signature']
secret = ENV['STORQ_WEBHOOK_SECRET']

if verify_webhook_signature(payload, signature, secret)
  # Process webhook
else
  head :unauthorized
end
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post('/webhooks/storq.io', (req, res) => {
  const payload = JSON.stringify(req.body);
  const signature = req.headers['storq.io-signature'];
  const secret = process.env.STORQ_WEBHOOK_SECRET;

  if (verifyWebhookSignature(payload, signature, secret)) {
    // Process webhook
    res.sendStatus(200);
  } else {
    res.sendStatus(401);
  }
});
import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature)

# In your webhook endpoint (Flask)
@app.route('/webhooks/storq.io', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('Storq-Signature')
    secret = os.environ['STORQ_WEBHOOK_SECRET']

    if verify_webhook_signature(payload, signature, secret):
        # Process webhook
        return '', 200
    else:
        return '', 401

Example: Processing Order Shipped Events

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def storq.io
    payload = request.body.read
    signature = request.headers['Storq-Signature']

    unless verify_signature(payload, signature)
      return head :unauthorized
    end

    event = JSON.parse(payload)

    case event['type']
    when 'order.shipped'
      handle_order_shipped(event['data']['object'])
    when 'stock.low'
      handle_stock_low(event['data']['object'])
    end

    head :ok
  end

  private

  def handle_order_shipped(order)
    customer = Customer.find_by(email: order['customer_email'])
    OrderMailer.shipped_notification(
      customer,
      order['tracking_number']
    ).deliver_later
  end

  def verify_signature(payload, signature)
    expected = OpenSSL::HMAC.hexdigest(
      'SHA256',
      ENV['STORQ_WEBHOOK_SECRET'],
      payload
    )
    Rack::Utils.secure_compare(expected, signature)
  end
end
const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.STORQ_WEBHOOK_SECRET;

app.post('/webhooks/storq.io', (req, res) => {
  const signature = req.headers['storq.io-signature'];
  const payload = JSON.stringify(req.body);

  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.sendStatus(401);
  }

  const event = req.body;

  switch (event.type) {
    case 'order.shipped':
      handleOrderShipped(event.data.object);
      break;
    case 'stock.low':
      handleStockLow(event.data.object);
      break;
  }

  res.sendStatus(200);
});

async function handleOrderShipped(order) {
  await sendEmail({
    to: order.customer_email,
    template: 'order_shipped',
    data: { tracking_number: order.tracking_number }
  });
}
import hmac
import hashlib
import json
import os
from flask import Flask, request

app = Flask(__name__)

WEBHOOK_SECRET = os.environ['STORQ_WEBHOOK_SECRET']

@app.route('/webhooks/storq.io', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('Storq-Signature')

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return '', 401

    event = json.loads(payload)

    if event['type'] == 'order.shipped':
        handle_order_shipped(event['data']['object'])
    elif event['type'] == 'stock.low':
        handle_stock_low(event['data']['object'])

    return '', 200

def handle_order_shipped(order):
    send_email(
        to=order['customer_email'],
        template='order_shipped',
        data={'tracking_number': order['tracking_number']}
    )

Best Practices

Testing Webhooks

Use the webhook testing tool in your dashboard to send test events to your endpoint.

Local development: Use tools like ngrok or LocalTunnel to expose your local server for webhook testing.