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 placeholder biteshipOrderId (prefixed pending-). 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-Signature header before trusting the payload.

Next