Signups
The waitlist resource: CRUD, notify-now, waitlist and variant notify, bulk delete, variant transfers, and the read-only notification send log.
BetaAPI v2 is in public beta: endpoints, fields and behaviour can change before general availability. See the changelog
Last updated
A signup is one customer waiting on one variant over one channel. Internally these are Intent rows; v1 exposed them as “intents”.
Path prefix: /back_in_stock/signups, under the base URL https://app.stoqapp.com/api/v2/external.
The status field
Section titled “The status field”Every signup carries a compiled lifecycle status, derived from its timestamps:
| Status | Meaning |
|---|---|
pending |
Waiting — no notification sent yet |
notified |
The back-in-stock alert was sent |
unsubscribed |
The contact opted out |
blocked |
A send was attempted but stopped (e.g. plan limits) |
Precedence when several timestamps are set: unsubscribed wins (the contact opted out), then notified, then blocked; a row with none of the three is pending.
The status filter on list counts the way the reports do, so a signup can match more than one value: pending is every signup not notified yet (the same pending as every back-in-stock report, so blocked and unsubscribed signups never notified are included), notified is every signup that was notified, and blocked and unsubscribed are every signup carrying that flag. A signup’s own status still shows its most specific state. To see why a send was blocked, filter the notification log with status=blocked.
List signups
Section titled “List signups”GET /back_in_stock/signups| Param | Type | Notes |
|---|---|---|
channel |
email / sms |
|
status |
pending / notified / blocked / unsubscribed |
|
shopify_variant_id |
gid or numeric | Shopify variant id |
shopify_product_id |
gid or numeric | Shopify product id |
email |
string | Partial match on the signup’s or its customer’s email |
phone |
string | Partial match on the signup’s or its customer’s phone |
from, to |
ISO 8601 | Window on created_at. A bare date (2026-09-14) is that whole day in the shop’s time zone |
page, per_page |
integer | per_page default 50, max 200 |
Aliases: “waitlist”, “show the waitlist”, “notify me list”, “who’s waiting”, “customers waiting for restock”
Response: { "signups": [...], "summary": {...}, "meta": { "total_count", "page", "per_page", "total_pages" } }.
Each signup has source, where it came from: restock-rocket-extension is the storefront form and restock-rocket-extension-resubscribe is Quick Resubscribe, which the list marks with an icon. product_deleted and variant_deleted are the list’s Deleted badges: product_deleted when every variant of the product is deleted from Shopify, otherwise variant_deleted when this variant is.
summary counts every signup that matches the filters, not only this page, by the same rules as the status filter. pending + notified = total, and pending equals the reports’ pending for the same signups; blocked and unsubscribed overlap them:
{ "summary": { "total": 1840, "pending": 1240, "notified": 600, "blocked": 12, "unsubscribed": 28 } }Read a signup
Section titled “Read a signup”GET /back_in_stock/signups/:id{ "id": "uuid", "status": "pending", "channel": "email", "contact": "jane@example.com", "quantity": 1, "variant": { "shopify_variant_id": "gid://shopify/ProductVariant/123", "shopify_product_id": "gid://shopify/Product/456", "title": "Small / Blue" }, "customer": { "id": "uuid", "email": "jane@example.com", "name": "Jane" }, "optin": { "required": false, "confirmed": null }, "notifications_sent": 0, "last_notified_at": null, "created_at": "2026-06-01T12:00:00Z"}optin.confirmed is only meaningful when the shop requires double opt-in (see Compliance settings); it is null otherwise.
Create a signup
Section titled “Create a signup”POST /back_in_stock/signupsAdmin-side waitlist creation, for example importing a list. Shoppers sign up through the storefront widget that the STOQ app already provides; do not use this endpoint as a storefront form, and see settings to configure that widget. Finds or creates the customer, then creates the signup.
| Body field | Required | Notes |
|---|---|---|
channel |
yes | email or sms |
shopify_variant_id |
yes | |
email |
for the email channel |
|
phone |
for the sms channel |
|
shopify_product_id |
no | Resolved from the variant when omitted |
quantity |
no | integer, default 1 |
name |
no | Customer name |
Returns: 201 Created with the full signup representation.
Notes: returns 409 Conflict when a pending signup already exists for the same contact + variant + channel — pending signups are unique per contact/variant/channel.
Delete a signup
Section titled “Delete a signup”DELETE /back_in_stock/signups/:idAliases: “remove from waitlist”, “cancel restock alert”, “take customer off the notify me list”
Notes: pending signups only — deleting an already-notified signup returns 409 Conflict (history is immutable).
Returns: { "deleted": true, "signup_id": "..." }.
Notify now
Section titled “Notify now”POST /back_in_stock/signups/:id/notifySends the back-in-stock notification for one signup immediately, regardless of stock state. Delivery runs through the normal notification pipeline.
| Body field | Type | Default | Notes |
|---|---|---|---|
allow_resend |
boolean | false | Required to re-notify an already-notified signup |
Aliases: “send the restock email”, “notify this customer”, “resend restock notification”
Notes: returns 409 Conflict when the signup was already notified and allow_resend is not set.
Returns: 202 Accepted with { "signup_id": "...", "queued": true }.
Notify the waitlist
Section titled “Notify the waitlist”POST /back_in_stock/signups/notifySends to every signup matching the waitlist filters (or only the first limit), as the dashboard’s Send notifications and Re-send all buttons do. Sends regardless of stock.
| Body field | Type | Notes |
|---|---|---|
status |
string | pending (not notified yet) or notified (re-send, oldest notification first when limit is set) |
channel |
string | email or sms: send only that channel |
limit |
integer | Send to only the first N matches |
customer_id, shopify_market_ids, title, sku, without_orders, from, to, shopify_product_id, shopify_variant_id |
Same filters as the waitlist list. from/to are shop-local days, matched against the signup date for pending and the notified date for notified. title and sku match the product title or SKU. without_orders keeps notified signups that led to no order |
Aliases: “notify the waitlist”, “notify everyone waiting”, “send restock emails to the whole waitlist”
Returns 202 Accepted with one send per variant. Each send’s id is the job id for GET /back_in_stock/sending_history/:id, and providers names who delivers each channel (stoq is STOQ’s own sender). Returns 422 when no signup matches or a channel cannot send (plan limit, channel off, awaiting review, or a provider that cannot deliver); nothing is queued.
Notify specific variants
Section titled “Notify specific variants”POST /back_in_stock/signups/notify_variantsSends to everyone waiting on up to 10 variants, as the products table’s Send notifications bulk action does.
| Body field | Required | Notes |
|---|---|---|
shopify_variant_ids |
yes | Array of variant ids (gid or numeric), max 10 |
Aliases: “notify these variants”, “send notifications for these variants”, “notify waitlists for several products”
Sends to pending signups only, on every channel, regardless of stock. A variant whose channel cannot send is skipped and its reason listed in errors; the others still send. Returns 202 Accepted with one send per variant queued (its id is the job id for GET /back_in_stock/sending_history/:id) and providers per channel, or 422 when nothing was queued.
Bulk delete
Section titled “Bulk delete”POST /back_in_stock/signups/bulk_delete| Body field | Required | Notes |
|---|---|---|
signup_ids |
yes | Array of signup ids, max 100 (one dashboard page) |
Aliases: “clear the waitlist”, “purge pending restock signups”
Notes: all-or-nothing. If any id is unknown, nothing is deleted and the 422 lists each one under failed[]. Cannot be undone.
Returns: { "deleted_count": N, "deleted_ids": [...] }.
Transfer
Section titled “Transfer”POST /back_in_stock/signups/transferMoves signups from one variant to another — for variant merges or replacements.
| Body field | Required | Notes |
|---|---|---|
from_shopify_variant_id |
one of the two from_ fields |
Moves this variant’s waitlist. Must differ from the target |
from_shopify_product_id |
one of the two from_ fields |
Given alone, moves every variant of the product. Resolved from the source variant when omitted |
to_shopify_variant_id |
yes | Must be a variant of this store, else 422 |
to_shopify_product_id |
no | Read from the target variant when omitted |
Aliases: “move the waitlist”, “merge waitlists”, “shift restock signups to a new variant” Notes: moves pending signups only; notified signups stay. A signup that would duplicate one already waiting on the target variant is dropped rather than moved. Moves of more than 500 signups run in the background.
Returns: 202 Accepted with { "count": N, "background": false }. count is the number queued to move, and background is true for large moves.
Notifications (the send log)
Section titled “Notifications (the send log)”A separate, read-only resource: one entry per delivery attempt, including sends that were blocked. Use it to answer “who actually got notified, and what didn’t go out”.
List notifications
Section titled “List notifications”GET /back_in_stock/notifications| Param | Type | Notes |
|---|---|---|
channel |
email / sms |
Old push sends still list with channel: push, but push is not a filter value |
status |
sent / blocked |
|
shopify_variant_id |
gid or numeric | Shopify variant id |
shopify_product_id |
gid or numeric | Shopify product id |
from, to |
ISO 8601 | Filters on when the send was attempted. A bare date (2026-09-14) is that whole day in the shop’s time zone |
group_by |
product |
Counts per product instead of rows. See below. |
page, per_page |
integer | per_page default 50, max 200 |
Aliases: “back in stock send log”, “who got notified”, “notification history”
Notes: status=blocked returns sends that did not go out, with the reason.
Response: { "notifications": [...], "summary": {...}, "meta": {...} }. Each entry has the shape shown under Read a notification.
summary counts every send that matches the filters, not only this page. It has the same shape as the summary under Counts per product: total, sent, blocked and blocked_by_reason. The rows are newest first, so one page can be all blocked while most sends that day went out. Read summary to answer “how many”.
Counts per product
Section titled “Counts per product”group_by=product returns counts instead of rows. The other filters still apply. Products are sorted by total, highest first, and paged with page and per_page; meta.total_count is the number of products.
{ "summary": { "total": 674, "sent": 612, "blocked": 62, "blocked_by_reason": [ { "blocked_reason_code": "plan_limit", "blocked_reason_label": "Plan limit reached", "count": 50, "example_reason": "Notification limit for plan reached. Not sending notification." }, { "blocked_reason_code": "email_bounced", "blocked_reason_label": "Email address bounced", "count": 12, "example_reason": "Customer email has bounced more than 2 times." } ] }, "products": [ { "shopify_product_id": "gid://shopify/Product/456", "product_title": "Nimbus Hoodie", "total": 120, "sent": 110, "blocked": 10, "blocked_by_reason": [ { "blocked_reason_code": "plan_limit", "blocked_reason_label": "Plan limit reached", "count": 10 } ] } ], "meta": { "total_count": 38, "page": 1, "per_page": 50, "total_pages": 1 }}Read a notification
Section titled “Read a notification”GET /back_in_stock/notifications/:id{ "id": "uuid", "status": "blocked", "blocked_reason": "Store is not allowed to send to country code 44.", "blocked_reason_code": "sms_country_not_allowed", "blocked_reason_label": "SMS not allowed to this country", "channel": "sms", "contact": "+447700900123", "shopify_variant_id": "gid://shopify/ProductVariant/123", "shopify_product_id": "gid://shopify/Product/456", "product_title": "Nimbus Hoodie", "variant_title": "Large", "signup_id": "uuid", "attempted_at": "2026-06-01T12:00:00Z", "sent_at": null}attempted_atis when STOQ tried to send.sent_atis when it went out; it isnullfor a blocked send.product_titleandvariant_titlecome from STOQ’s copy of the product. They arenullwhen STOQ has no record of the variant.blocked_reasonis the stored text and can name a country code, keyword or template position.blocked_reason_codegroups those texts;blocked_reason_labelsays it in plain words. Both arenullfor a sent notification.
blocked_reason_code |
blocked_reason_label |
|---|---|
plan_limit |
Plan limit reached |
template_not_approved |
Template not approved yet |
email_bounced |
Email address bounced |
customer_unsubscribed_product |
Customer unsubscribed from product alerts |
signup_unsubscribed |
Signup unsubscribed |
customer_unsubscribed_sms |
Customer unsubscribed from SMS |
customer_unsubscribed_email |
Customer unsubscribed from email |
sms_country_not_allowed |
SMS not allowed to this country |
sms_restricted_keyword |
SMS text has a restricted word |
variant_disabled |
Product alerts turned off for this variant |
flow_trigger_off |
Shopify Flow trigger is off |
provider_not_verified |
Klaviyo or Postscript setup not verified |
provider_error |
Sending provider returned an error |
other |
Other reason |
A stored text that matches no known pattern reads as other.
signup_id links back to the signup the notification was sent for. For aggregate send counts over time, use the notifications report instead.
Thanks for letting us know.
Still stuck? Ask Quinn or chat with support