> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loopreturns.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ship by Loop shipment

> | Trigger                              | What it means                                          |
| ------------------------------------- | ------------------------------------------------------- |
| `ship-by-loop.services.requested`     | A shipment was created that this provider can fulfill.  |
| `ship-by-loop.purchase.requested`     | Services have been selected and are ready for purchase. |
| `ship-by-loop.cancellation.requested` | The shipment was cancelled.                             |

- **`ship-by-loop.services.requested`**: submit the services this provider can
  offer for the shipment's packages with
  `POST /ship-by-loop/shipments/{shipmentId}/services`. The shopper-facing
  portal polls for a response roughly every 5 seconds, up to 12 times
  (roughly 30-60 seconds total). If nothing arrives in that window, the
  shopper falls back to whichever standard (non-carrier-choice) return
  methods were already available — no error is shown. The API still
  accepts a `POST .../services` call after that window, but the
  portal has stopped polling for it by then, so the shopper who already
  fell back will not see it.
- **`ship-by-loop.purchase.requested`**: record the resulting purchase, including
  tracking and label details, with
  `POST /ship-by-loop/shipments/{shipmentId}/orders`.
- **`ship-by-loop.cancellation.requested`**: stop any work in progress for this
  shipment and void/refund any carrier label already purchased. Loop does not
  expect a response beyond the `200` acknowledgement.

Acknowledge every delivery with a `200` response.

### Package field state by trigger

`service`, `label_url`, `qr_code_url`, and `tracking_number` on each
`packages` entry are populated over the lifetime of a shipment rather
than all at once:

| Trigger                    | `service`  | `label_url` | `qr_code_url` | `tracking_number` |
| --------------------------- | ---------- | ------------ | -------------- | ------------------ |
| `services.requested`       | `null`      | `null`       | `null`         | `null`             |
| `purchase.requested`       | set          | `null`       | `null`         | `null`             |
| `cancellation.requested`   | see below   | see below    | see below      | see below          |

`service` is `null` at `services.requested` (nothing has been chosen
yet) and always set by `purchase.requested` — Loop only asks a
provider to purchase once every package has a service selected.
`label_url` / `qr_code_url` / `tracking_number` stay `null` through
both of those triggers; they're populated only once the provider
calls `POST .../orders`, which happens after `purchase.requested` is
received. For `cancellation.requested`, every field reflects whatever
had already been recorded before the cancellation — `null` for any of
them the provider hadn't gotten to yet.




## OpenAPI

````yaml webhook ShipByLoopShipment
openapi: 3.1.0
info:
  title: Ship by Loop Shipment Webhook
  version: 1.0.0
  description: >
    This notification is sent when a Ship by Loop shipment reaches a state that
    an

    external shipping provider can act on. Every delivery includes a "trigger"

    property identifying which event produced it.


    Your webhook URL must be reachable over HTTPS and must respond to an `HTTP
    HEAD`

    request (Loop sends one when the subscription is created, to verify the
    URL).


    | Request Header   | Description               |

    | ---------------- | ------------------------- |

    | X-Loop-Signature | Hashed Webhook Secret Key |


    The X-Loop-Signature is a SHA-256 HMAC hash of the body payload,
    base64-encoded,

    using the Webhook Secret provided by Loop. See

    [Validate webhooks from Loop](/webhook-reference/validation) for the full

    verification steps.


    Note: Before attempting to create your hash of the POST request body, make
    sure slashes

    have been escaped & newlines have been removed from the JSON.
servers: []
security: []
paths: {}

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.