fulkruma.shipment.created.v1
Fires when a new shipment is created in Fulkruma — either by a direct POST /api/v1/shipments call, by the Plugipay-checkout webhook for an order containing physical items, or by a Storlaunch product order routed through Pattern 2. This is the canonical "we have a parcel to track now" signal: subscribe to mirror shipment rows into your own fulfilment dashboard or notify the buyer their order is being prepared.
When it fires
Inside the same Prisma transaction as the Shipment insert — you'll see the event before any subsequent status transition. There is exactly one emission per shipment ID; retries reuse the same evt_….
The shipment's initial status is pending while we wait for the Biteship adapter (Phase F) to send the order upstream, or confirmed once the courier acknowledges. The event fires at insert time regardless of which status path the shipment then walks.
Payload
{
"id": "evt_01HXAB7K3M9N2P5QRS8TVWXY3Z",
"type": "fulkruma.shipment.created.v1",
"occurredAt": "2026-05-12T10:42:00.123Z",
"accountId": "acc_01HX...",
"data": {
"shipmentId": "ship_01HXAB7K3M9N2P5QRS8TVWXY3Z",
"checkoutSessionId": "cs_01HX...",
"courierCode": "jne",
"status": "pending"
}
}
The data field is the minimal shipment summary — just the IDs and the courier code. Fetch the full shipment via GET /api/v1/shipping/shipments/:id if you need the destination, items, or label.
Handler examples
// Node
import express from 'express';
import { verifyWebhook } from '@forjio/fulkruma-node';
app.post('/webhooks/fulkruma', express.raw({ type: 'application/json' }), async (req, res) => {
const event = verifyWebhook({
rawBody: req.body, // the raw Buffer
signature: req.header('Fulkruma-Signature'),
secret: process.env.FULKRUMA_WEBHOOK_SECRET,
}); // throws on a bad signature → answer 400
if (event.type === 'fulkruma.shipment.created.v1') {
const { shipmentId, checkoutSessionId, courierCode } = event.data;
await orders.markShipping(checkoutSessionId, { fulkrumaShipmentId: shipmentId, courier: courierCode });
}
res.status(200).end();
});
# Python
if event["type"] == "fulkruma.shipment.created.v1":
d = event["data"]
orders.mark_shipping(d["checkoutSessionId"], fulkruma_shipment_id=d["shipmentId"], courier=d["courierCode"])
// Go
if event.Type == "fulkruma.shipment.created.v1" {
var d struct{ ShipmentID, CheckoutSessionID, CourierCode, Status string }
_ = json.Unmarshal(event.Data, &d)
orders.MarkShipping(ctx, d.CheckoutSessionID, d.ShipmentID, d.CourierCode)
}
What to do
- Mark the order "shipping" in your own DB; surface the Fulkruma shipment ID to support agents.
- Email the buyer that the parcel is in motion (use the in-shipment customer email or pull from your CRM).
- Schedule a follow-up to fetch the label and waybill once they're available (see
/shipping/shipments/:id/label). - Increment a "shipments in flight" analytics counter.
Common pitfalls
- Treating the event as
confirmed. Phase E records the shipment with a placeholderbiteshipOrderId(prefixedpending-). The courier hasn't accepted yet. Watch for the status transition rather than assuming the event means dispatched. - Charging shipping fees here. Shipping price was set on the originating Plugipay checkout. Re-charging here would double-bill.
- Skipping signature verification. Always verify the
Fulkruma-Signatureheader before trusting the payload.
Related events
fulkruma.product.created.v1— the product whose stock the shipment will consume.fulkruma.stock.adjusted.v1— the movement that fires when the shipment ships.fulkruma.shipment.pickup_confirmed.v1— the draft was booked with the courier.fulkruma.shipment.status_updated.v1— every courier status after that, includingdeliveredandreturned(there is no separate delivered event).fulkruma.shipment.cancelled.v1andfulkruma.shipment.rebooked.v1.
Next
- Webhooks reference — signature verification, retries, ordering.
- Shipments resource — the full object shape and write API.