GraphQL API
Warehouse API
Warehouse guide

Event subscriptions

Deliver signed resource events with optional GraphQL projections.

Choose the delivery scope

Manage subscriptions through this schema's GraphQL endpoint. Subscriptions and stored queries inherit its warehouse scope; they do not receive an warehouseId variable.

https://api.pack.cloud/v1/warehouses/{warehouseId}/graphql

The Organization and Warehouse APIs use the same management operation names. Their event topics, server variables, and projection schema differ. Topic, action, and trigger values are case-sensitive.

TopicActionsServer variable
Shipmentcreate, updateshipmentId
Fulfillmentcreate, updatefulfillmentId
Picklistcreate, updatepicklistId
GoodsReceiptcreate, updategoodsReceiptId
ReturnReceiptcreate, updatereturnReceiptId
StockMutationcreatestockMutationId

Create a subscription

Create requires a unique key, a supported topic, at least one action, and an HTTPS uri. Delivery is enabled by default when enabled is omitted. The key cannot be changed later.

Destinations must use HTTPS. PackCloud rejects URLs containing credentials or fragments, as well as private or otherwise unsafe destinations.

mutation CreateSubscription($input: EventSubscriptionCreateInput!) {
  eventSubscriptionCreate(input: $input) {
    eventSubscription {
      id
      key
      topic
      actions
      triggers
      uri
      enabled
      status
    }
    secret
  }
}
Variables
{
  "input": {
    "key": "fulfillment-updates",
    "topic": "Fulfillment",
    "actions": ["create", "update"],
    "triggers": ["fulfillment.status", "fulfillment.lines.quantity"],
    "uri": "https://events.example.com/packcloud/fulfillments",
    "query": "query FulfillmentEvent($fulfillmentId: ID!) { fulfillment(id: $fulfillmentId) { id reference status } }",
    "queryFilter": "fulfillment.reference:*",
    "enabled": true
  }
}

PackCloud generates the signing secret. It is returned only by create and rotation and cannot be queried later. Store it immediately; rotate it if it is lost.

Open the generated eventSubscriptionCreate reference for the current input and response types.

Select update triggers

Triggers apply only to the update action. Omit triggers to receive every update for the topic. When supplied, an update is delivered when at least one changed trigger matches your list. Create events are never filtered by triggers.

Relation-only paths such as fulfillment.lines select membership changes. More specific paths such as fulfillment.lines.quantity select changes to that field.

Shipment update triggers
Shipment fields
  • shipment.barcode
  • shipment.trackingCode
  • shipment.trackingUrl
  • shipment.labelType
  • shipment.piecesCount
  • shipment.reference
  • shipment.insuranceAmount
  • shipment.status
  • shipment.shippingStatus.code
  • shipment.shippingPhase.code
  • shipment.type
  • shipment.customsInvoiceNumber
  • shipment.customsShipmentType
  • shipment.canceledAt
  • shipment.brand.id
  • shipment.customer.id
  • shipment.carrier.code
  • shipment.shippingService.id
  • shipment.shippingServiceMethod.id
Sender address
  • shipment.senderAddress
  • shipment.senderAddress.id
  • shipment.senderAddress.companyName
  • shipment.senderAddress.name
  • shipment.senderAddress.address
  • shipment.senderAddress.address2
  • shipment.senderAddress.houseNumber
  • shipment.senderAddress.postalCode
  • shipment.senderAddress.city
  • shipment.senderAddress.stateCode
  • shipment.senderAddress.countryCode
  • shipment.senderAddress.email
  • shipment.senderAddress.phone
  • shipment.senderAddress.eori
  • shipment.senderAddress.vat
  • shipment.senderAddress.oss
Return address
  • shipment.returnAddress
  • shipment.returnAddress.id
  • shipment.returnAddress.companyName
  • shipment.returnAddress.name
  • shipment.returnAddress.address
  • shipment.returnAddress.address2
  • shipment.returnAddress.houseNumber
  • shipment.returnAddress.postalCode
  • shipment.returnAddress.city
  • shipment.returnAddress.stateCode
  • shipment.returnAddress.countryCode
  • shipment.returnAddress.email
  • shipment.returnAddress.phone
  • shipment.returnAddress.eori
  • shipment.returnAddress.vat
  • shipment.returnAddress.oss
Recipient address
  • shipment.recipientAddress
  • shipment.recipientAddress.id
  • shipment.recipientAddress.companyName
  • shipment.recipientAddress.name
  • shipment.recipientAddress.address
  • shipment.recipientAddress.address2
  • shipment.recipientAddress.houseNumber
  • shipment.recipientAddress.postalCode
  • shipment.recipientAddress.city
  • shipment.recipientAddress.stateCode
  • shipment.recipientAddress.countryCode
  • shipment.recipientAddress.email
  • shipment.recipientAddress.phone
  • shipment.recipientAddress.eori
  • shipment.recipientAddress.vat
  • shipment.recipientAddress.oss
Delivery address
  • shipment.deliveryAddress
  • shipment.deliveryAddress.id
  • shipment.deliveryAddress.companyName
  • shipment.deliveryAddress.name
  • shipment.deliveryAddress.address
  • shipment.deliveryAddress.address2
  • shipment.deliveryAddress.houseNumber
  • shipment.deliveryAddress.postalCode
  • shipment.deliveryAddress.city
  • shipment.deliveryAddress.stateCode
  • shipment.deliveryAddress.countryCode
  • shipment.deliveryAddress.email
  • shipment.deliveryAddress.phone
  • shipment.deliveryAddress.eori
  • shipment.deliveryAddress.vat
  • shipment.deliveryAddress.oss
Contents
  • shipment.contents
  • shipment.contents.quantity
  • shipment.contents.description
  • shipment.contents.price
  • shipment.contents.weight
  • shipment.contents.countryCodeOfOrigin
  • shipment.contents.hsCode
Pieces
  • shipment.pieces
  • shipment.pieces.barcode
  • shipment.pieces.trackingUrl
  • shipment.pieces.dimensions
  • shipment.pieces.weight
  • shipment.pieces.shippingPhase.code
Fulfillment update triggers
Fulfillment fields
  • fulfillment.reference
  • fulfillment.status
  • fulfillment.releaseAt
  • fulfillment.shippedAt
  • fulfillment.organization.id
Lines
  • fulfillment.lines
  • fulfillment.lines.quantity
  • fulfillment.lines.product.id
Allocations
  • fulfillment.allocations
  • fulfillment.allocations.quantity
  • fulfillment.allocations.quantityException
  • fulfillment.allocations.quantityPicked
  • fulfillment.allocations.quantityShipped
  • fulfillment.allocations.product.id
  • fulfillment.allocations.stock.id
  • fulfillment.allocations.originalStock.id
Picklist update triggers
Picklist fields
  • picklist.reference
  • picklist.status
  • picklist.type
  • picklist.assignedToUser.id
  • picklist.picklistPreset.id
  • picklist.trolley.id
GoodsReceipt update triggers
Goods receipt fields
  • goodsReceipt.remarks
  • goodsReceipt.status
  • goodsReceipt.expectedDeliveryAt
  • goodsReceipt.arrivedAt
  • goodsReceipt.completedAt
  • goodsReceipt.organization.id
Pieces
  • goodsReceipt.pieces
  • goodsReceipt.pieces.carrierCode
  • goodsReceipt.pieces.barcode
  • goodsReceipt.pieces.status
  • goodsReceipt.pieces.type
  • goodsReceipt.pieces.arrivedAt
  • goodsReceipt.pieces.completedAt
Lines
  • goodsReceipt.lines
  • goodsReceipt.lines.goodsReceiptPiece.id
  • goodsReceipt.lines.product.id
  • goodsReceipt.lines.quantityReceived
ReturnReceipt update triggers
Return receipt fields
  • returnReceipt.remarks
  • returnReceipt.status
  • returnReceipt.undeliverableReturnReason
  • returnReceipt.completedAt
  • returnReceipt.returnRequest.id
  • returnReceipt.organization.id
Lines
  • returnReceipt.lines
  • returnReceipt.lines.condition
  • returnReceipt.lines.remarks
  • returnReceipt.lines.quantityReceived
  • returnReceipt.lines.returnRequestLine.id
StockMutation — create only; no update triggers

A delivery can contain multiple concrete paths in fields_changed. These paths identify the changed records and fields more precisely than the trigger catalog.

[
  "fulfillment[id: '<fulfillment-id>'].status",
  "fulfillment[id: '<fulfillment-id>'].lines[id: '<line-id>'].quantity"
]

Read and change subscriptions

List subscriptions with cursor pagination, or retrieve one by ID.

query GetAndListSubscriptions($subscriptionId: ID!) {
  eventSubscription(id: $subscriptionId) {
    id
    key
    topic
    actions
    triggers
    uri
    enabled
    status
    pauseReason
    pausedAt
  }
  eventSubscriptions(first: 20) {
    edges { node { id key topic enabled status } }
    pageInfo { hasNextPage endCursor }
  }
}
{ "subscriptionId": "<event-subscription-id>" }

Update only the fields that should change. A valid update that corrects a structural problem makes the subscription active again while preserving its key and secret.

mutation UpdateSubscription($subscriptionId: ID!, $input: EventSubscriptionUpdateInput!) {
  eventSubscriptionUpdate(id: $subscriptionId, input: $input) {
    eventSubscription { id key uri enabled status pauseReason }
    userErrors { field message }
  }
}
{
  "subscriptionId": "<event-subscription-id>",
  "input": {
    "uri": "https://events.example.com/packcloud/current",
    "enabled": true
  }
}

Always request userErrors from update and deactivate mutations.

Rotate or deactivate

Rotation replaces only the secret. Store the replacement before discarding the old value.

mutation RotateSubscriptionSecret($subscriptionId: ID!) {
  eventSubscriptionRotateSecret(id: $subscriptionId) {
    eventSubscription { id key status }
    secret
  }
}

Deactivation disables and pauses the subscription while retaining its delivery history.

mutation DeactivateSubscription($subscriptionId: ID!) {
  eventSubscriptionDeactivate(id: $subscriptionId) {
    eventSubscription { id key enabled status pauseReason pausedAt }
    userErrors { field message }
  }
}
{ "subscriptionId": "<event-subscription-id>" }
There is no delete or replay mutation. Re-enabling a subscription affects future matching events; events excluded while it was inactive are not replayed.

Add a stored GraphQL projection

The optional query adds its result as data in the delivery. Without a stored query, the envelope omits data.

Stored projections can read the complete current Warehouse Query schema, including sensitive fields. Create subscriptions only for trusted receivers and select only the data they need.
  • Provide exactly one named, read-only query operation.
  • Mutations, subscriptions, and introspection are rejected.
  • Use only the server variable listed for the selected topic.
  • The query runs without a user identity and uses the endpoint's scope.
  • Every connection must include exactly one literal first or last integer from 1 through 250. Omitting both or using a pagination variable is unsupported.
Query size16 KiB (16,384 bytes) or less
Depth7 levels or less
Root fieldsAt most 5
Computed cost250 points or less
Connection pageA literal page size from 1 through 250
Delivery payloadAt most 1 MiB for the complete envelope

PackCloud supplies the declared resource variable from the event.

Filter projected data

An optional queryFilter uses the same Shopify-style grammar as PackCloud search queries. It requires a stored query, and every filter field must be a scalar response path selected by that query. Aliases become part of the response path.

fulfillment.reference:*
fulfillment.status:shipped AND fulfillment.reference:"FUL-1001"
(fulfillment.status:shipped OR fulfillment.status:canceled) AND fulfillment.reference:FUL*
fulfillment.reference:>=FUL-1000
-fulfillment.reference:*
  • Use field:value for equality and field:>value, :>=, :<, or :<= for ordering.
  • Use AND, OR, NOT, a leading minus sign, parentheses, quoted values, and a trailing * for prefix matching. A standalone *tests whether a scalar is present; negate that term to test for an absent or null value.
  • AND binds more tightly than OR.
  • For list paths, a term matches when any selected list member matches.

A non-match is filtered before transport, so no request is sent for that subscription and event.

Receive and verify deliveries

PackCloud sends an HTTPS POST with an application/json body.

{
  "event_id": "<event-id>",
  "delivery_id": "<delivery-id>",
  "topic": "Fulfillment",
  "action": "update",
  "subscription_key": "fulfillment-updates",
  "occurred_at": "<ISO-8601 timestamp>",
  "fields_changed": ["fulfillment[id: '<fulfillment-id>'].status"],
  "query_variables": { "fulfillmentId": "<fulfillment-id>" },
  "data": {
    "fulfillment": {
      "id": "<fulfillment-id>",
      "reference": "<fulfillment-reference>",
      "status": "<fulfillment-status>"
    }
  }
}

data appears only when a stored query is configured and can be null.

Event-IdStable event ID; use it to correlate related deliveries
Event-Delivery-IdUnique delivery ID; use it for idempotent processing
Event-TimestampUnix timestamp of occurred_at, unchanged on retries
Event-SignatureBase64-encoded HMAC-SHA256 of the exact raw body bytes
Event-TopicTopic for the event
Event-Actioncreate or update
Event-Subscription-KeySubscription key
Event-Resource-IdID of the event's root resource

Verify the signature against the raw request body and compare it in constant time.

$expected = base64_encode(hash_hmac('sha256', $rawBody, $secret, true));
$valid = hash_equals($expected, $receivedSignature);
Do not decode and re-encode JSON before verification. PackCloud freezes the destination, exact body bytes, system headers, timestamp, and signature once; every retry sends those same values.

Retry failed deliveries

SuccessAny 2xx response
RetriedConnection or DNS failures, 408, 429, and 5xx responses
Failed immediatelyRedirects and every other non-2xx response
AttemptsAt most 10 total; delays double from 1 minute up to a 24-hour maximum
RetentionEvents, deliveries, and attempt history expire after 30 days

Redirects are not followed. Deduplicate with delivery_id because transport retries are expected. There is no manual replay or payload recomputation.

After repeated terminal failures, PackCloud temporarily stops sending while continuing to queue matching deliveries. It tests the oldest pending delivery periodically and resumes oldest-first delivery after a successful response. Projection errors do not affect this recovery behavior.

Inspect delivery history

Delivery history is read-only, newest first, and remains available through the subscription until its 30-day retention expires. Attempts are ordered from oldest to newest.

query DeliveryHistory($subscriptionId: ID!) {
  eventSubscription(id: $subscriptionId) {
    deliveries(first: 20) {
      edges {
        node {
          id
          eventId
          destination
          status
          attemptCount
          nextAttemptAt
          completedAt
          lastErrorCode
          lastError
          expiresAt
          createdAt
          attempts {
            attemptNumber
            outcome
            startedAt
            completedAt
            httpStatus
            errorCode
            error
          }
        }
      }
      pageInfo { hasNextPage endCursor }
    }
  }
}

Use status, nextAttemptAt, and the error fields to troubleshoot a delivery. Open the generated EventDelivery type reference for the current statuses and field types.

Fix projection failures

A structural failure means the stored document or its shape is no longer valid for the current schema. PackCloud records the failed delivery and pauses the subscription because another record cannot fix it. Correct the query or filter with a valid update to reactivate delivery.

A record-specific resolver, data, or payload-size failure affects only that delivery. It does not pause the subscription.