storlaunch.product.created.v1
Fires when a product is created in the workspace — either via POST /v1/storefront/products, the dashboard's Products → New action, or the AI-quick-add flow. The event reflects the state at the moment the row was committed; subsequent edits arrive as storlaunch.product.updated.v1.
Subscribe if you mirror the catalogue into another system (Fulkruma, Ripllo, your own search index, a price-comparison feed). Storlaunch already wires Fulkruma + Ripllo system endpoints to this event when their modules are on; you don't need to handle the same fan-out yourself unless you have a third destination.
When it fires
In every code path that writes a Product row with archived: false:
POST /v1/storefront/productssucceeds.- The dashboard's quick-create or AI generator commits a product.
- The CSV-import path inserts a row.
Fire is fire-and-forget — the API call returns 201 before the outbox flushes. Delivery is at-least-once; dedupe on event.id.
Payload
{
"id": "evt_01HX...",
"type": "storlaunch.product.created.v1",
"createdAt": "2026-05-13T10:42:00Z",
"accountId": "acc_01HX...",
"data": {
"id": "prod_01HX...",
"accountId": "acc_01HX...",
"name": "Field Notes Notebook",
"slug": "field-notes-notebook",
"sku": null,
"description": "Pocket-sized, dot-grid, made in Bandung.",
"type": "physical",
"weight": 120,
"length": 140,
"width": 90,
"height": 12,
"licenseEnabled": false,
"maxActivations": 1,
"archived": false,
"priceCents": 75000,
"currency": "IDR",
"imageUrl": "https://cdn.../thumb.jpg",
"available": false,
"tags": ["stationery"],
"brand": null,
"googleProductCategory": null
}
}
Note: priceCents is the smallest-unit price (IDR has no fractional unit, so this is just rupiah). available is the derived "published && !archived" flag.
Handler examples
// Node
import { verifyWebhook } from '@forjio/storlaunch/webhooks';
app.post('/webhooks/storlaunch', async (req, res) => {
const event = verifyWebhook(req.rawBody, req.headers['x-storlaunch-signature'], process.env.STORLAUNCH_WEBHOOK_SECRET);
if (event.type === 'storlaunch.product.created.v1') {
const p = event.data;
await searchIndex.upsert({ id: p.id, name: p.name, tags: p.tags, priceCents: p.priceCents });
}
res.status(200).end();
});
# Python
from storlaunch.webhooks import verify_webhook
@app.post("/webhooks/storlaunch")
def handle(req):
event = verify_webhook(req.body, req.headers["x-storlaunch-signature"], WEBHOOK_SECRET)
if event["type"] == "storlaunch.product.created.v1":
p = event["data"]
search_index.upsert(id=p["id"], name=p["name"], tags=p["tags"], price_cents=p["priceCents"])
return ("", 200)
// Go
event, err := storlaunch.VerifyWebhook(body, r.Header.Get("X-Storlaunch-Signature"), secret)
if err != nil { http.Error(w, "bad sig", 401); return }
if event.Type == "storlaunch.product.created.v1" {
var p storlaunch.Product
_ = json.Unmarshal(event.Data, &p)
search.Upsert(ctx, p.ID, p.Name, p.Tags, p.PriceCents)
}
w.WriteHeader(200)
What to do
- Mirror the row into your search index, ERP, or analytics warehouse.
- If
available: true, queue any "new product" buyer notifications. - Tag the row in your CRM if
metadatacarries a campaign source.
Common pitfalls
- Treating
available: falseas "archived".availableispublished && !archived. Afalseproduct just hasn't been put live yet. - Assuming
imageUrlis permanent. Thumbnails on Storlaunch's CDN may be re-signed/migrated. Re-resolve from the payload on each read rather than caching the URL. - Skipping dedupe. Outbox is at-least-once. Use
event.idas a unique key in your processed-events table. - Reacting to your own writes. If your CRM also creates products via API, guard against round-trips with a
metadata.sourcetag.
Related events
storlaunch.product.updated.v1— subsequent edits.storlaunch.product.archived.v1— soft-delete.storlaunch.variant.created.v1— variants added to this product later.