Skip to content

REST Hooks

Introduction

REST Hooks itself is not a specification, but rather a collection of patterns that treat webhooks like subscriptions. the REST Hook subscriptions are created, updated or deleted using a REST API.

With REST Hooks, the REST API is able to communicate with other apps in real time, via webhooks, without a complicated setup.

The REST Hooks pattern has four basic requirements

  • Mechanism to store subscriptions
  • Mechanism to modify subscriptions via API
  • List of event types & implementation of events
  • Mechanism to send hooks

Available events

EventDescription
order.createdTriggered when a new order is created.
order.updatedTriggered when an existing order is updated.
order.paidTriggered when an order is marked as paid.
product.inventory.lowTriggered when a product inventory becomes low.
upsell.acceptedTriggered when a customer accepts an upsell.
app.uninstalledTriggered when a seller uninstalls your app.
app.charge_updatedTriggered when the status of one of your app charges changes.

The events order.create, inventory.low, and upsell.accept are deprecated aliases. Do not subscribe to them in new code.

Payload format

Every delivery is a POST request with a JSON body in this shape:

json
{
  "event_name": "order.created",
  "event_happened_at": "2026-08-08T21:30:00.000000Z",
  "data": {}
}

data holds the event object: the order for order.* events, the charge for app.charge_updated, and so on. Read your event data from data.

A trimmed order.created delivery:

json
{
  "event_name": "order.created",
  "event_happened_at": "2026-08-08T21:30:00.000000Z",
  "data": {
    "id": "00000000-0000-0000-0000-000000000000",
    "ref": "10245",
    "status": 1,
    "total": 249.9,
    "currency": "MAD",
    "customer": { "id": "...", "first_name": "...", "email": "..." },
    "store_id": "00000000-0000-0000-0000-000000000000"
  }
}

An app.uninstalled delivery:

json
{
  "event_name": "app.uninstalled",
  "event_happened_at": "2026-08-08T21:30:00.000000Z",
  "data": {
    "store_id": "00000000-0000-0000-0000-000000000000",
    "store_slug": "my-store",
    "seller_id": "00000000-0000-0000-0000-000000000000",
    "app_id": "app_xxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
}

Security & Verification

YouCan Signature

Every delivery carries a X-YOUCAN-SIGNATURE header: an hmac sha256 hash of the raw request body, signed with the secret of the OAuth client the subscription belongs to.

Compute the hash over the raw request body, before any JSON parsing. Re-encoding a parsed body does not reproduce the same bytes.

Validating the signature in PHP:

PHP
function isValidYouCanSignature(string $signature, string $rawBody, string $signingKey): bool
{
    $expectedSignature = hash_hmac(
        'sha256', // Hashing Algorithm
        $rawBody, // Raw request body
        $signingKey // OAuth Client Secret Key
    );

    return hash_equals($expectedSignature, $signature);
}

The same validation in TypeScript, as used by the app template:

ts
import { Buffer } from 'node:buffer';
import crypto from 'node:crypto';

function isValidYouCanSignature(signature: string, rawBody: string, signingKey: string): boolean {
  const expected = crypto.createHmac('sha256', signingKey).update(rawBody).digest('hex');

  const received = Buffer.from(signature);
  const computed = Buffer.from(expected);

  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

Delivery headers

Each delivery also carries these headers:

HeaderContent
X-YOUCAN-SIGNATUREThe signature of the payload.
X-YOUCAN-TOPICThe event name.
X-YOUCAN-DELIVERY-IDA unique id for this delivery. Retries of the same delivery keep the same id, use it to deduplicate.
X-YOUCAN-API-VERSIONThe payload version. Currently v1.

Delivery and retries

  • Your endpoint must respond with a 2xx status code. Respond quickly and do the work after.
  • If your endpoint responds with a 5xx status code, or the request does not complete, the delivery is retried up to 5 times with an increasing delay, over approximately 4 hours.
  • If your endpoint responds with a 4xx status code, the delivery is not retried.
  • If your endpoint responds with 410 Gone, the subscription is deactivated.
  • Subscriptions that fail for a long period are deactivated. We email you when that happens.
  • To restore a deactivated subscription, make sure the endpoint accepts deliveries again, then subscribe to the same event and address again. For webhooks declared in an app manifest, releasing a version of the app activates its subscriptions again.
  • Each app (OAuth client) holds at most 7 active subscriptions per event per store. Deactivated subscriptions do not count.