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}/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 |
|---|---|---|
| Shipment | create, update | shipmentId |
| Fulfillment | create, update | fulfillmentId |
| Picklist | create, update | picklistId |
| GoodsReceipt | create, update | goodsReceiptId |
| ReturnReceipt | create, update | returnReceiptId |
| StockMutation | create | stockMutationId |
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
}
}{
"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.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
Fulfillment update triggers
fulfillment.referencefulfillment.statusfulfillment.releaseAtfulfillment.shippedAtfulfillment.organization.id
fulfillment.linesfulfillment.lines.quantityfulfillment.lines.product.id
fulfillment.allocationsfulfillment.allocations.quantityfulfillment.allocations.quantityExceptionfulfillment.allocations.quantityPickedfulfillment.allocations.quantityShippedfulfillment.allocations.product.idfulfillment.allocations.stock.idfulfillment.allocations.originalStock.id
Picklist update triggers
picklist.referencepicklist.statuspicklist.typepicklist.assignedToUser.idpicklist.picklistPreset.idpicklist.trolley.id
GoodsReceipt update triggers
goodsReceipt.remarksgoodsReceipt.statusgoodsReceipt.expectedDeliveryAtgoodsReceipt.arrivedAtgoodsReceipt.completedAtgoodsReceipt.organization.id
goodsReceipt.piecesgoodsReceipt.pieces.carrierCodegoodsReceipt.pieces.barcodegoodsReceipt.pieces.statusgoodsReceipt.pieces.typegoodsReceipt.pieces.arrivedAtgoodsReceipt.pieces.completedAt
goodsReceipt.linesgoodsReceipt.lines.goodsReceiptPiece.idgoodsReceipt.lines.product.idgoodsReceipt.lines.quantityReceived
ReturnReceipt update triggers
returnReceipt.remarksreturnReceipt.statusreturnReceipt.undeliverableReturnReasonreturnReceipt.completedAtreturnReceipt.returnRequest.idreturnReceipt.organization.id
returnReceipt.linesreturnReceipt.lines.conditionreturnReceipt.lines.remarksreturnReceipt.lines.quantityReceivedreturnReceipt.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>" }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.
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: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": "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.
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. 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.