Docs/Events/Shipment events

Shipment events#

Shipment events are generated by carrier scans as a parcel moves through its lifecycle, plus any custom events you emit yourself. Use the catalogue below to find the event you care about, then wire the event group into your notification flow.

Key concepts#

Four fields describe every shipment event. Understanding how they relate is the fastest way to wire notifications correctly.

FieldWhat it isExample
event_nameThe specific carrier scan. One per physical checkpoint; ~160 distinct values.DEPARTURE_FROM_TRANSPORT_HUB
phaseThe lifecycle stage the parcel is in. ~10 values; think of it as a progress bar.in_transit
event_groupThe notification bucket. This is what fires your Klaviyo/webhook/email flow.shipment_in_transit
directionWhether the parcel is going to the customer or back to the merchant.merchant_customer

How they combine: one event_name always maps to exactly one phase and (when notifying) to one event_group. The same event_name can belong to different event_groups depending on direction — see below.

Ref pattern: shipments/{phase}/{event_name} — e.g. shipments/delivered/SUCCESSFULLY_DELIVERED.

Filter on event_group when subscribing to webhooks or building Klaviyo flows — there are ~20 groups vs ~160 event names, and the groups already bundle events that should trigger the same customer message.

One notification per group#

Karla fires at most one notification per event_group, per shipment, per channel (webhook, Klaviyo, Brevo, etc.). Notifications follow the shipment forward through its lifecycle: a group notifies the first time it is reached and never again — so several events that map to the same group produce a single notification, and once a later stage is reached, earlier groups don't re-fire. You get one message per meaningful state change, and you don't need to deduplicate on your side. For webhooks this applies to the default dedup_enabled: true; a webhook created with dedup_enabled: false skips this rule and receives every raw event.

A few merchant-facing groups are designed to repeat and bypass this rule:

  • shipment_carrier_delay, shipment_damaged, and shipment_delayed_due_to_customer_request.
  • shipment_eta_updated — a fallback group: when an incoming event is suppressed by the one-per-group rule but the shipment's estimated arrival changed, Karla notifies shipment_eta_updated instead, so it can fire several times as the ETA moves. It is suppressed once the parcel is waiting for collection (pickup point, parcel shop, parcel locker) or has been left with a neighbour, in the letterbox, or with a delivery notice card, and once the shipment is delivered, returned, or cancelled. It isn't tied to a lifecycle phase (so it's not shown in the catalogue below). On webhooks with dedup_enabled: true, such a delivery arrives with event_group set to shipment_eta_updated (both top-level and inside event_data), while ref and event_data.event_name still name the underlying carrier event. Webhooks with dedup_enabled: false always carry the underlying event's group.

Forward vs return direction#

Every shipment has a direction:

  • merchant_customer (forward) — the default; merchant ships to customer. Groups prefixed shipment_* apply.
  • customer_merchant (return) — customer ships back to merchant. Karla collapses the forward groups into 5 return_shipment_* buckets so you can wire a separate template for "your return is on its way" vs "your order is on its way".

The underlying carrier events (OUT_FOR_DELIVERY, SUCCESSFULLY_DELIVERED, etc.) are identical — only the group assignment differs.

direction is declared when the shipment is created — it records what the sender set out to do, and carrier scans never change it. Don't confuse it with the is_return flag on the tracking object, which means something else entirely: see direction vs is_return.

  • Draft shipments (no tracking number yet) only notify on ORDER_CREATED (shipment_order_placed), ORDER_PROCESSED (shipment_pre_transit), and ORDER_CANCELLED (shipment_order_cancelled).
  • Fulfilled shipments (tracking number attached) generate the full set of carrier tracking events below.

Event catalogue#

Browse every shipment event Karla emits. Toggle Forward / Return to switch direction, and By group / By event to pivot between notification buckets and individual event names. Use search to filter by any text.

114 events in 21 groups··

Returns to sender (RTS)#

A return to sender happens when a forward shipment (merchant_customer) can't be delivered and the carrier sends it back. It plays out on the same forward shipment, which moves into the returned phase — it is not a separate customer_merchant return shipment (a customer shipping a product back), which uses the return_shipment_* groups described above.

Karla splits RTS into three groups by cause:

  • shipment_failed_returned — delivery was attempted but failed (undeliverable, or attempts exhausted).
  • shipment_refused_then_returned — the recipient refused the parcel.
  • shipment_not_picked_up_then_returned — the recipient never collected it from a pickup point.

direction vs is_return#

Two fields mention returns and they answer different questions.

directionis_return
Where it comes fromDeclared when the shipment is created (sender's intent)Reported by the carrier on the parcel (DHL's returnFlag, AfterShip's return_to_sender)
Question it answersIs this a return leg someone deliberately created?Is this parcel currently travelling back to the sender?
Customer-initiated return (own new label)customer_merchantusually null or false
Return to sender (undeliverable outbound)stays merchant_customertrue when the carrier flags it
ReliabilityAuthoritativeBest-effort hint
  • To detect a genuine return — a customer shipping goods back on a return label — check direction == "customer_merchant". That is the only reliable signal, and it is available from the moment the return shipment exists.
  • To detect a return to sender — check for the returned / return_failed phase, or for one of the three RTS event groups listed above.

Despite the name, is_return says nothing about whether a return was requested. It is a carrier-observed hint that the parcel is heading back to the sender — which in practice is almost always a failed outbound delivery, i.e. an RTS on a merchant_customer shipment.

It is unreliable in the other direction too: DHL reports returnFlag: false even on DHL Retoure products, so a genuine customer_merchant return usually carries is_return: null. Where no carrier flag exists, Karla falls back to the shipment's phase — and because a customer_merchant return moves through the ordinary in_transit / delivered phases, that fallback also yields false for a real return still in flight.

Treat is_return as supplementary colour on an RTS, never as the test for "is this a return".

This section covers shipment_failed_returned, which bundles nine events:

EventMeaning
RETURN_IN_PROGRESSParcel is moving back through the carrier network toward the sender.
SHIPMENT_RETURNING_TO_SENDERParcel is on its final leg back — out for delivery to, or waiting at a pickup point for, the sender.
SHIPMENT_SUCCESSFULLY_RETURNED_TO_SENDERKarla-confirmed success: the parcel was delivered or collected back at the sender.
SHIPMENT_RETURNED_TO_SENDERThe carrier's own native "returned" status, passed through as-is.
RETURN_TO_SENDER_DEPARTED_FROM_PROCESSING_CENTERParcel left a carrier processing center on its way back to the sender.
RETURN_TO_SENDER_ARRIVED_AT_PROCESSING_CENTERParcel reached a carrier processing center on its way back to the sender.
RETURN_TO_SENDER_OUT_FOR_DELIVERYParcel is out for delivery back to the sender.
RETURN_TO_SENDER_INCORRECT_ADDRESSCarrier is returning the parcel because the address is incorrect. The parcel is still in progress.
RETURN_TO_SENDER_INSUFFICIENT_PAYMENTCarrier is returning the parcel because of insufficient payment.

RETURN_TO_SENDER_FAILED is a different case: a delivery attempt on the return leg failed, and the shipment moves to the return_failed phase. It is not in any notification group, so it appears in the event history but does not trigger a notification.

Typical order#

When a carrier reports the full journey, the events fire in this order:

RETURN_IN_PROGRESS → SHIPMENT_RETURNING_TO_SENDER → a terminal event (SHIPMENT_SUCCESSFULLY_RETURNED_TO_SENDER or SHIPMENT_RETURNED_TO_SENDER).

This ordering describes the recorded event history — every event that arrives appears in a webhook's context.shipments[].events[] and in the portal timeline. It does not mean several notifications: the whole group still fires a single notification (see One notification per group above).

These events are derived from the carrier's return-leg scans, so which ones fire — and in what order — depends entirely on the carrier. Many carriers skip the intermediate steps and report only a single terminal event. In particular, RETURN_IN_PROGRESS is not guaranteed for every return: a carrier that doesn't emit in-transit scans on the return leg will never produce it.

SHIPMENT_RETURNED_TO_SENDER vs SHIPMENT_SUCCESSFULLY_RETURNED_TO_SENDER#

Both are terminal returned-phase events meaning the parcel is back with the sender, but they come from different sources:

  • SHIPMENT_SUCCESSFULLY_RETURNED_TO_SENDER is Karla-derived: it fires when a successful delivery / collection scan arrives while the parcel is on the return leg. It is the most reliable confirmation that a return actually completed.
  • SHIPMENT_RETURNED_TO_SENDER is a carrier's native "returned to sender" status, passed through as-is. It reflects whatever the carrier reports rather than a confirmed delivery scan.

Which of the two you receive depends on the carrier's own event vocabulary. A shipment usually gets one or the other; occasionally both.

Triggering a workflow (RTS)#

Karla sends only one notification per group (see One notification per group above), so a shipment produces a single shipment_failed_returned notification — the first return event to arrive fires it, and the rest are suppressed. Bind your RTS workflow (e.g. notifying a warehouse) to the group rather than a specific event name: which of the nine events triggers the notification is carrier-dependent, but with deduplication enabled (the default for webhooks) you'll only receive one, so no client-side deduplication is needed. In that notification, event_group is shipment_failed_returned and event_data.event_name tells you which event fired.

Shipment Events API#

You can programmatically trigger events on shipments via the API reference. This is useful for:

  • Firing notification flows or webhooks for draft shipments that don't yet have carrier tracking.
  • Injecting state from a logistics partner that doesn't integrate with Karla.
  • Recording internal QA or pre-handoff milestones.

Lookup by order number#

curl -X POST "https://api.gokarla.io/v1/shops/{slug}/shipments/events?notify=true" \
  -u your-username:your-private-api-key \
  -H "Content-Type: application/json" \
  -d '{
    "id": "1001",
    "id_type": "order_number",
    "event_name": "ORDER_PROCESSED"
  }'

Lookup by external order ID#

curl -X POST "https://api.gokarla.io/v1/shops/{slug}/shipments/events?notify=true" \
  -u your-username:your-private-api-key \
  -H "Content-Type: application/json" \
  -d '{
    "id": "5512345678901",
    "id_type": "external_order_id",
    "event_name": "SUCCESSFULLY_DELIVERED"
  }'

Order-level lookups — order_number, external_order_id, or order_uuid as id_type — only work reliably when the order has exactly one shipment. For multi-shipment orders, identify the specific shipment via shipment_uuid, tracking_number, or external_shipment_id instead.

The notify=true query parameter triggers the event group notification (webhook, email flow, etc.). Without it, the event is recorded silently.

event_data shape#

Variables inside event_data are what Karla exposes to native integration templates (Klaviyo, Emarsys, Brevo, Braze, HubSpot, etc.) unless documented otherwise.

{
  ...
  "event_data": {
    "shipment_id": "abc65a96-0871-452a-a506-c644e2012123",
    "carrier_reference": "dhl",
    "carrier_display_name": "DHL",
    "event_name": "DEPARTURE_FROM_TRANSPORT_HUB",
    "phase": "in_transit",
    "tracking_number": "0123456789",
    "tracking_url": "https://example.com/tracking",
    "updated_at": "2024-01-29T14:48:47+00:00",
    "event_group": "shipment_in_transit",
    "direction": "merchant_customer",
    "order_number": "ORD-2024-001",
    "order_number_urlencoded": "ORD-2024-001",

    // Optional fields present depending on event type or shop-provided data
    "order_name": "ORD-2024-001",
    "zip_code": "10115",
    "shipping_address": "123 Main St, Berlin, 10115, Germany",
    "total_order_value": 49.99,
    "order_currency": "EUR",
    "order_status_url": "https://shop.example.com/orders/status/123",
    "trackpage_token": "7f3a8b2c1d9e4f6a5b8c7d0e3f2a1b4c",
    "trackpage_url": "https://app.gokarla.io/track/your-shop-slug?orderNumber=ORD-2024-001&token=7f3a8b2c1d9e4f6a5b8c7d0e3f2a1b4c",
    "external_customer_id": "CUST-98765",
    "external_order_id": "shopify-order-123",
    "preferred_delivery_date": "15.01.2024",
    "customer_first_name": "John",
    "customer_last_name": "Doe",
    "customer_country": "Germany",

    // Optional fields for specific event groups
    "expected_delivery_date": "20.01.2024",
    "pick_up_address": "Parcel Shop, 456 Store St, Berlin",
    "pick_up_until": "25.01.2024",
    "neighbour_name": "Jane Smith",
    "requested_delivery_date": "30.01.2024",

    // Optional fields for multi-shipment orders (IN_TRANSIT events)
    "number_of_shipments": 3,
    "other_tracking_numbers": ["1234567890", "0987654321"]
  }
  ...
}

Full payload example#

A real shipment event as you'd receive it via webhook, including the context block with the full order, customer, and shipment data:

{
  "source": "shipments",
  "ref": "shipments/in_transit/DEPARTURE_FROM_TRANSPORT_HUB",
  "version": 1,
  "triggered_at": "2024-01-29T14:48:47+00:00",
  "event_group": "shipment_in_transit",
  "event_data": {
    "shipment_id": "6be0ea64-fe5e-478e-aee5-f9f7bbc53804",
    "carrier_reference": "dhl",
    "event_name": "DEPARTURE_FROM_TRANSPORT_HUB",
    "phase": "in_transit",
    "tracking_number": "0123456789",
    "tracking_url": "https://example.com/tracking",
    "updated_at": "2024-01-29T14:48:47+00:00",
    "event_group": "shipment_in_transit",
    "direction": "merchant_customer"
  },
  "context": {
    "order": {
      "order_number": "0000001",
      "order_name": null,
      "order_placed_at": "2025-06-18T22:00:04.264555+00:00",
      "total_order_price": 123.456,
      "shipping_price": 4.99,
      "sub_total_price": 118.457,
      "discount_price": 30,
      "products": [
        {
          "title": "Delivery socks",
          "quantity": 2,
          "price": 1,
          "size": "S",
          "images": [
            {
              "src": "https://storage.googleapis.com/karla-merchants-metadata/gokarla/Karla_SINGLE_PRODUCT.png",
              "alt": "Delivery socks"
            }
          ],
          "sku": null,
          "weight": null,
          "tax_lines": [],
          "bundled_products": [],
          "shipment_id": null,
          "type": "product"
        }
      ],
      "discounts": [],
      "email_id": null,
      "address": {
        "address_line_1": "Gormannstr.",
        "address_line_2": "19a",
        "city": "Berlin",
        "country": "Germany",
        "country_code": null,
        "name": "John Doe",
        "phone": "123456789",
        "province": "Berlin",
        "province_code": null,
        "street": "Gormannstr. 19a",
        "zip_code": "01234",
        "company": null
      },
      "currency": "EUR",
      "segments": null,
      "weight": null,
      "external_customer_id": null,
      "order_status_url": null
    },
    "customer": {
      "external_id": null,
      "email": null,
      "first_name": null,
      "last_name": null,
      "full_name": "John Doe",
      "phone": "123456789"
    },
    "shipments": [
      {
        "uuid": "6be0ea64-fe5e-478e-aee5-f9f7bbc53804",
        "updated_at": "2024-01-29T14:48:47+00:00",
        "events": [
          {
            "time": "2023-10-08T13:50:40+00:00",
            "timezone": "UTC",
            "location": null,
            "additional_info": null,
            "phase": "in_transit",
            "event_name": "DEPARTURE_FROM_TRANSPORT_HUB",
            "event_strings": {
              "event_status": "Moving on! Your parcel has left the transport hub.",
              "list_label": "Arriving 25.09",
              "header_headline": "IN TRANSIT",
              "header_title": "25.09",
              "header_subtitle": ""
            },
            "language": "en"
          },
          {
            "time": "2023-10-07T12:01:10+00:00",
            "timezone": "UTC",
            "location": null,
            "additional_info": null,
            "phase": "order_processed",
            "event_name": "ORDER_PROCESSED",
            "event_strings": {
              "event_status": "Your parcel has been packed and is ready to be shipped.",
              "list_label": "packed",
              "header_headline": "PACKED",
              "header_title": "Your parcel has been packed",
              "header_subtitle": ""
            },
            "language": "en"
          },
          {
            "time": "2023-10-06T18:58:15+00:00",
            "timezone": "UTC",
            "location": null,
            "additional_info": null,
            "phase": "order_created",
            "event_name": "ORDER_CREATED",
            "event_strings": {
              "event_status": "You've placed an online order.",
              "list_label": "Order placed",
              "header_headline": "ORDER PLACED",
              "header_title": "Thanks for shopping!",
              "header_subtitle": ""
            },
            "language": "en"
          }
        ],
        "estimated_arrival": {
          "start": "2023-09-23T12:00:00+00:00",
          "end": "2023-09-25T12:00:00+00:00",
          "time_prediction": "25.09",
          "language": "en"
        },
        "carrier": {
          "tracking_number": "0123456789",
          "carrier_reference": "dhl",
          "tracking_url": null
        },
        "flag": "normal",
        "pickup": null,
        "products": [
          {
            "title": "Delivery socks",
            "quantity": 2,
            "price": 1,
            "size": "S",
            "images": [
              {
                "src": "https://storage.googleapis.com/karla-merchants-metadata/gokarla/Karla_SINGLE_PRODUCT.png",
                "alt": "Delivery socks"
              }
            ],
            "sku": null,
            "weight": null,
            "tax_lines": [],
            "bundled_products": []
          }
        ]
      }
    ],
    "claims": []
  },
  "shop_slug": "gokarla",
  "shop_id": "7af5390b-1425-4af6-a00d-e5f5184a7b51"
}
Was this helpful?