Event subscriptions
Receive a signed webhook when an order, shipment or other resource changes.
Choose the delivery scope
Manage subscriptions through this schema's GraphQL endpoint. Subscriptions and stored queries inherit its organization scope; they do not receive an organizationId variable.
https://api.pack.cloud/v1/organizations/{organizationId}/graphqlThe 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.
| Topic | Actions | Server variable |
|---|---|---|
| Order | create, update | orderId |
| ReturnRequest | create, update | returnRequestId |
| Shipment | create, update | shipmentId |
| Product | create, update | productId |
| PurchaseOrder | create, update | purchaseOrderId |
| ReturnRefund | create, update | returnRefundId |
| Customer | create, update | customerId |
Create a subscription
Create requires a unique key, a supported topic, at least one action, and an HTTPS uri. Actions that include update also require at least one trigger. 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
}
}{
"input": {
"key": "order-updates",
"topic": "Order",
"actions": ["create", "update"],
"triggers": ["order.status", "order.lines.quantityShipped"],
"uri": "https://events.example.com/packcloud/orders",
"query": "query OrderEvent($orderId: ID!) { order(id: $orderId) { id reference status lines(first: 25) { edges { node { id quantityShipped } } } } }",
"queryFilter": "order.status:shipped",
"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.
The eventSubscriptionCreate reference lists every input field and the full response.
Select update triggers
Triggers apply only to the update action: a subscription whose actions include update requires at least one trigger, and a create-only subscription may not have any. 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 order.lines select membership changes. More specific paths such as order.lines.quantityShipped select changes to that field.
Every prefix with deeper triggers also has a wildcard form, such as order.*, order.lines.* and order.billingAddress.*. A wildcard selects every change beneath that prefix, and on a relation it also covers that relation gaining or losing a record, so order.lines.* covers a line being added or removed as well as any of a line's fields changing. Use order.* to follow every update on the topic, and a wildcard keeps selecting fields that are added to the catalog later. A relation without deeper triggers, such as order.events, has no wildcard form.
Paths ending in .events select new history entries on the resource, such as an order being held or released.
Use order.exceptions to be alerted when an order is held, released or gets an exception.
Order update triggers
order.*order.billingAddress.*order.brand.*order.customer.*order.exceptions.*order.integration.*order.lines.*order.lines.parentLine.*order.lines.product.*order.recipientAddress.*order.shippingServiceMethod.*
order.integrationReferenceorder.quantityOrderedorder.quantityAllocatedorder.quantityShippedorder.quantityUnallocatedorder.referenceorder.shippingPriceorder.statusorder.customsInvoiceNumberorder.customsShipmentTypeorder.localeorder.canceledAtorder.shippedAtorder.brand.idorder.customer.idorder.integration.idorder.shippingServiceMethod.id
order.billingAddressorder.billingAddress.idorder.billingAddress.companyNameorder.billingAddress.nameorder.billingAddress.addressorder.billingAddress.address2order.billingAddress.houseNumberorder.billingAddress.postalCodeorder.billingAddress.cityorder.billingAddress.stateCodeorder.billingAddress.countryCodeorder.billingAddress.emailorder.billingAddress.phoneorder.billingAddress.eoriorder.billingAddress.vatorder.billingAddress.oss
order.recipientAddressorder.recipientAddress.idorder.recipientAddress.companyNameorder.recipientAddress.nameorder.recipientAddress.addressorder.recipientAddress.address2order.recipientAddress.houseNumberorder.recipientAddress.postalCodeorder.recipientAddress.cityorder.recipientAddress.stateCodeorder.recipientAddress.countryCodeorder.recipientAddress.emailorder.recipientAddress.phoneorder.recipientAddress.eoriorder.recipientAddress.vatorder.recipientAddress.oss
order.linesorder.lines.parentLine.idorder.lines.product.idorder.lines.nameorder.lines.priceorder.lines.priceInclVatorder.lines.quantityOrderedorder.lines.quantityAllocatedorder.lines.quantityShippedorder.lines.quantityUnallocated
order.exceptionsorder.exceptions.typeorder.exceptions.messageorder.exceptions.adviceorder.exceptions.dataorder.exceptions.ignoredAt
order.events
ReturnRequest update triggers
returnRequest.*returnRequest.brand.*returnRequest.customer.*returnRequest.lines.*returnRequest.lines.orderLine.*returnRequest.lines.product.*returnRequest.lines.returnReason.*returnRequest.lines.subReturnReason.*returnRequest.returnShippingServiceMethod.*returnRequest.senderAddress.*
returnRequest.referencereturnRequest.remarksreturnRequest.returnMethodreturnRequest.returnShippingPricereturnRequest.returnShippingPaymentStatusreturnRequest.statusreturnRequest.refundTypereturnRequest.localereturnRequest.completedAtreturnRequest.brand.idreturnRequest.customer.idreturnRequest.returnShippingServiceMethod.id
returnRequest.senderAddressreturnRequest.senderAddress.idreturnRequest.senderAddress.companyNamereturnRequest.senderAddress.namereturnRequest.senderAddress.addressreturnRequest.senderAddress.address2returnRequest.senderAddress.houseNumberreturnRequest.senderAddress.postalCodereturnRequest.senderAddress.cityreturnRequest.senderAddress.stateCodereturnRequest.senderAddress.countryCodereturnRequest.senderAddress.emailreturnRequest.senderAddress.phonereturnRequest.senderAddress.eorireturnRequest.senderAddress.vatreturnRequest.senderAddress.oss
returnRequest.linesreturnRequest.lines.remarksreturnRequest.lines.quantityExpectedreturnRequest.lines.quantityReceivedreturnRequest.lines.product.idreturnRequest.lines.orderLine.idreturnRequest.lines.returnReason.idreturnRequest.lines.subReturnReason.id
returnRequest.events
Shipment update triggers
shipment.*shipment.brand.*shipment.carrier.*shipment.contents.*shipment.customer.*shipment.deliveryAddress.*shipment.pieces.*shipment.pieces.shippingPhase.*shipment.recipientAddress.*shipment.returnAddress.*shipment.senderAddress.*shipment.shippingPhase.*shipment.shippingService.*shipment.shippingServiceMethod.*shipment.shippingStatus.*
shipment.barcodeshipment.trackingCodeshipment.trackingUrlshipment.labelTypeshipment.piecesCountshipment.referenceshipment.insuranceAmountshipment.statusshipment.shippingStatus.codeshipment.shippingPhase.codeshipment.typeshipment.customsInvoiceNumbershipment.customsShipmentTypeshipment.canceledAtshipment.brand.idshipment.customer.idshipment.carrier.codeshipment.shippingService.idshipment.shippingServiceMethod.id
shipment.senderAddressshipment.senderAddress.idshipment.senderAddress.companyNameshipment.senderAddress.nameshipment.senderAddress.addressshipment.senderAddress.address2shipment.senderAddress.houseNumbershipment.senderAddress.postalCodeshipment.senderAddress.cityshipment.senderAddress.stateCodeshipment.senderAddress.countryCodeshipment.senderAddress.emailshipment.senderAddress.phoneshipment.senderAddress.eorishipment.senderAddress.vatshipment.senderAddress.oss
shipment.returnAddressshipment.returnAddress.idshipment.returnAddress.companyNameshipment.returnAddress.nameshipment.returnAddress.addressshipment.returnAddress.address2shipment.returnAddress.houseNumbershipment.returnAddress.postalCodeshipment.returnAddress.cityshipment.returnAddress.stateCodeshipment.returnAddress.countryCodeshipment.returnAddress.emailshipment.returnAddress.phoneshipment.returnAddress.eorishipment.returnAddress.vatshipment.returnAddress.oss
shipment.recipientAddressshipment.recipientAddress.idshipment.recipientAddress.companyNameshipment.recipientAddress.nameshipment.recipientAddress.addressshipment.recipientAddress.address2shipment.recipientAddress.houseNumbershipment.recipientAddress.postalCodeshipment.recipientAddress.cityshipment.recipientAddress.stateCodeshipment.recipientAddress.countryCodeshipment.recipientAddress.emailshipment.recipientAddress.phoneshipment.recipientAddress.eorishipment.recipientAddress.vatshipment.recipientAddress.oss
shipment.deliveryAddressshipment.deliveryAddress.idshipment.deliveryAddress.companyNameshipment.deliveryAddress.nameshipment.deliveryAddress.addressshipment.deliveryAddress.address2shipment.deliveryAddress.houseNumbershipment.deliveryAddress.postalCodeshipment.deliveryAddress.cityshipment.deliveryAddress.stateCodeshipment.deliveryAddress.countryCodeshipment.deliveryAddress.emailshipment.deliveryAddress.phoneshipment.deliveryAddress.eorishipment.deliveryAddress.vatshipment.deliveryAddress.oss
shipment.contentsshipment.contents.quantityshipment.contents.descriptionshipment.contents.priceshipment.contents.weightshipment.contents.countryCodeOfOriginshipment.contents.hsCode
shipment.piecesshipment.pieces.barcodeshipment.pieces.trackingUrlshipment.pieces.dimensionsshipment.pieces.weightshipment.pieces.shippingPhase.code
shipment.events
Product update triggers
product.*product.barcodes.*product.partOfBundles.*product.partOfBundles.bundleProduct.*product.partOfBundles.product.*product.parts.*product.parts.bundleProduct.*product.parts.product.*
product.brandNameproduct.codeproduct.costPriceproduct.countryCodeOfOriginproduct.descriptionproduct.dimensionsproduct.hsCodeproduct.nameproduct.priceproduct.purchaseAdviceModeproduct.typeproduct.weight
product.barcodesproduct.barcodes.barcodeproduct.barcodes.quantity
product.partOfBundlesproduct.partOfBundles.bundleProduct.idproduct.partOfBundles.product.idproduct.partOfBundles.quantity
product.partsproduct.parts.bundleProduct.idproduct.parts.product.idproduct.parts.quantity
PurchaseOrder update triggers
purchaseOrder.*purchaseOrder.lines.*purchaseOrder.lines.product.*purchaseOrder.supplier.*
purchaseOrder.referencepurchaseOrder.statuspurchaseOrder.supplier.id
purchaseOrder.linespurchaseOrder.lines.pricepurchaseOrder.lines.product.idpurchaseOrder.lines.quantityOrderedpurchaseOrder.lines.quantityReceivedpurchaseOrder.lines.sellAhead
purchaseOrder.events
ReturnRefund update triggers
returnRefund.*returnRefund.adjustments.*returnRefund.lines.*returnRefund.lines.returnRequestLine.*returnRefund.order.*returnRefund.returnRequest.*
returnRefund.codereturnRefund.methodreturnRefund.order.idreturnRefund.pricereturnRefund.remarksreturnRefund.returnRequest.idreturnRefund.shippingPricereturnRefund.statusreturnRefund.type
returnRefund.adjustmentsreturnRefund.adjustments.descriptionreturnRefund.adjustments.price
returnRefund.linesreturnRefund.lines.pricereturnRefund.lines.quantityreturnRefund.lines.returnRequestLine.id
returnRefund.events
Customer update triggers
customer.*customer.billingAddress.*customer.recipientAddress.*
customer.reference
customer.billingAddresscustomer.billingAddress.idcustomer.billingAddress.companyNamecustomer.billingAddress.namecustomer.billingAddress.addresscustomer.billingAddress.address2customer.billingAddress.houseNumbercustomer.billingAddress.postalCodecustomer.billingAddress.citycustomer.billingAddress.stateCodecustomer.billingAddress.countryCodecustomer.billingAddress.emailcustomer.billingAddress.phonecustomer.billingAddress.eoricustomer.billingAddress.vatcustomer.billingAddress.oss
customer.recipientAddresscustomer.recipientAddress.idcustomer.recipientAddress.companyNamecustomer.recipientAddress.namecustomer.recipientAddress.addresscustomer.recipientAddress.address2customer.recipientAddress.houseNumbercustomer.recipientAddress.postalCodecustomer.recipientAddress.citycustomer.recipientAddress.stateCodecustomer.recipientAddress.countryCodecustomer.recipientAddress.emailcustomer.recipientAddress.phonecustomer.recipientAddress.eoricustomer.recipientAddress.vatcustomer.recipientAddress.oss
A delivery reports the concrete paths in fields_changed, grouped by what happened to them. A record added to a relation lands in added, a record removed from one lands in removed, and a changed field value lands in updated. All three lists are always present and any of them can be empty. These paths identify the changed records and fields more precisely than the trigger catalog.
{
"added": ["order[id: '<order-id>'].lines[id: '<added-line-id>']"],
"updated": [
"order[id: '<order-id>'].status",
"order[id: '<order-id>'].lines[id: '<line-id>'].quantityShipped"
],
"removed": ["order[id: '<order-id>'].lines[id: '<removed-line-id>']"]
}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>" }Add a stored GraphQL projection
The optional query adds its result as data in the delivery. Without a stored query, the envelope omits data.
- 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
firstorlastinteger from 1 through 250. Omitting both or using a pagination variable is unsupported.
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.
order.status:shipped
order.status:shipped AND order.reference:"WEB-1001"
(order.status:shipped OR order.status:canceled) AND order.reference:WEB*
order.reference:>=WEB-1000
-order.reference:*- Use
field:valuefor equality andfield:>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. ANDbinds more tightly thanOR.- 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": "Order",
"action": "update",
"subscription_key": "order-updates",
"occurred_at": "<ISO-8601 timestamp>",
"fields_changed": {
"added": ["order[id: '<order-id>'].lines[id: '<added-line-id>']"],
"updated": [
"order[id: '<order-id>'].status",
"order[id: '<order-id>'].lines[id: '<line-id>'].quantityShipped"
],
"removed": []
},
"query_variables": { "orderId": "<order-id>" },
"data": {
"order": { "id": "<order-id>", "reference": "WEB-1001", "status": "shipped" }
}
}fields_changed always carries added, updated and removed. On a create delivery, added holds the path of the new resource and the other two lists are empty. data appears only when a stored query is configured and can be null.
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);Retry failed deliveries
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. The EventDelivery type reference reference lists all statuses and fields.
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.