Webhooks

GAINS publishes webhooks for specific events to allow for real-time integration to client endpoints

Webhook subscriptions allow for automated notifications to your designated systems on the occurrence of certain triggers in GAINS.

The current available webhook triggers are:

TriggerSetting nameDataType
Submission of Purchase Orders (PO's)POSubmitpurchase_order
Submission of Transfer Orders (TO's)TOSubmittransfer_order
Submission of Work Orders (WO's)WOSubmitwork_order
Changes to SKU-Location's inventory policy dataInventoryPolicyUpdateinventory_policy
Changes to a SKU's weekly forecast dataForecastWeeklyUpdateforecast
Changes to a SKU's monthly forecast dataForecastMonthlyUpdateforecast
Changes to task statusTaskStatusUpdatetask

Multiple Webhooks

As of v2025.10, it is possible to configure multiple webhooks, each with a unique set of properties. This allows for the same event to be sent to multiple destinations or for events to be sent to specific destinations.

Multiple webhooks are supported as shown:

[
  {
    "Name": "PrimaryDestination",
    //webhook properties
  },
  {
    "Name": "SecondaryDestination",
    //webhook properties
  }
]

Webhooks Properties

Webhook notifications are issued based on the settings you have defined for the following:

  • Payload Max Items - allows you to limit the number of records produced in the payload
    • For replenishment triggers such as new purchase, transfer and work orders, this represents the number of orders
    • For forecast and inventory update triggers, this represents the number of SKU-Locations
  • Payload Interval - define the gap (in seconds) between notifications
  • Retry Attempts - after a failed attempt, specify the number of times we should attempt to re-send a payload
  • Retry Gap - specify the time (in minutes) between retry attempts

Activating Webhooks, Triggers & Filters

There are two discrete but related actions in relation to receiving webhooks.

Parameter:Active

  • Turning this to true will begin transmission of items in the queue to the destination URL
  • Turning this to false will stop transmission of items in the queue but will not clear the queue of any items that were already queued.

Parameter:Triggers.Trigger.Enabled

  • Changing any of the triggers to true will look for those types of events and add them to the queue, to be sent to the destination if Active is also set to true
  • Changing any of the triggers to false will no longer look for those types of events and therefore, nothing additional for that event will be added to the queue
  • In v2025.09 and prior versions, the parameter was Triggers.Trigger = true/false

Parameter:Triggers.Trigger.Filter

  • As of v2025.10, it is possible to define a query filter on a trigger such that the trigger will only fire if the filter conditions are met. For instance, if we have multiple webhooks set up and want replenishment data for a range of locations to be sent to webhook 1 and the rest to webhook 2, we would set up the following:
[
  {
    "Name": "PrimaryDestination", //your friendly name
    "Active": true,
    "PayloadMaxItems": 100,
    "PayloadInterval": 60,
    "Url": "https://example.com/primary",
    "Authorization": "Bearer abc123",
    "Triggers": {
      //...
      "InventoryPolicyUpdate": {
        "Enabled": true,
        "Filter": "LocationCode >= 1000 and LocationCode <= 1999"
      },
      //...
    },
    "RetryHandling": {
      "Attempts": 0,
      "Gap": 10
    }
  },
  {
    "Name": "SecondaryDestination", //your friendly name
    "Active": true,
    "PayloadMaxItems": 100,
    "PayloadInterval": 60,
    "Url": "https://example.com/secondary",
    "Authorization": "Bearer abc123",
    "Triggers": {
      //...
      "InventoryPolicyUpdate": {
        "Enabled": true,
        "Filter": "LocationCode >= 2000"
      },
      //...
    },
    "RetryHandling": {
      "Attempts": 0,
      "Gap": 10
    }
  }
]
  • Acceptable filter expressions are those that are existing payload attributes of that specific payload. For instance, LocationCode is an attribute for the InventoryPolicy type

Webhook Behavior

A single webhook notification will only contain one record type - either PO's, TO's, WO's, SKU-Location inventory policy, forecasts or task status updates.

The queue processing rules are as follows:

  • Messages are processed in the order they enter the queue (FIFO)
  • When processing a message type, the system will include as many records of that type as possible up to PayloadMaxItems
  • Once a batch is full or no more records of that type are available, we move to the next message type in the queue
  • Any remaining records of the same type will be processed in subsequent queue iterations

Example - let's say we have the following items in the queue, starting from the first to enter the queue:

1. 10 POs (first messages in queue)
2. 12 TOs
3. 2 WOs
4. 10 POs
5. 50 SKULs (last messages in queue)

Let's assume that the PayloadMaxItems attribute is set to 15.

All successive payloads will respect PayloadInterval parameter.


Payload Envelope

Every webhook is delivered as an HTTP POST with Content-Type: application/json. If you have configured an Authorization value for the destination, it is sent in the Authorization header.

All payloads share the same envelope: a DataType naming the subject, and a Data array holding the records.

{
  "DataType": "purchase_order",
  "Data": [
    {"TrackingNumber": 123, ...},
    {"TrackingNumber": 124, ...},
    {"TrackingNumber": 125, ...}
  ]
}

A few conventions apply to every sample below:

  • Data is always an array, even when it holds a single record
  • Dates are YYYY-MM-DD and timestamps are YYYY-MM-DD HH:MM:SS, in the GAINS server timezone
  • Attributes with no value are sent as null rather than omitted. The exception is ExtendedData, which is omitted entirely where you have configured no extended data for that level
  • ExtendedData holds your own configured attributes, keyed by the API label you set for the payload (see Extended Data). It is always optional, and the same payload can carry it at more than one level - see the table below
  • Field names use PascalCase by default. A destination configured for camelCase receives the same structure and the same values with camelCase keys (dataType, data, lineItems, itemCode). Values such as purchase_order and Completed are not affected

ExtendedData is marked // optional wherever it appears in the samples below. These are the levels it can be configured at:

PayloadLevels where ExtendedData can appear
Purchase, transfer and work ordersOrder header, line item
Inventory policyItem, location
Forecast (weekly and monthly)Item, location
Task statusNot supported

Where you have configured extended data for a level, every record at that level carries ExtendedData, holding one entry per configured attribute. An attribute with no value for that particular record is sent as null rather than dropped, so the set of keys is the same on every record.


Webhook Samples

Order payloads (POSubmit, TOSubmit, WOSubmit)

A webhook is issued when replenishment orders are submitted in GAINS. Orders are submitted in two ways, and both raise the webhook identically:

  • automatically, by the Order Auto Submit job
  • manually, when a user submits orders in the GAINS UI

All three order triggers share one structure: an order header carrying the shipping and receiving locations, the dates and the address block, with a LineItems array beneath it. The line items are identical across the three; the only difference is in the header, where a transfer order carries SenderName in place of the VendorName a purchase or work order carries.

{
  "DataType": "purchase_order",
  "Data": [
    {
      "TrackingNumber": 4821,
      "ShippingLocation": "ACME",
      "ReceivingLocation": "CHI01",
      "VendorName": "Acme Supplies",
      "ReleaseDate": "2026-05-21",
      "RequestedDeliveryDate": "2026-06-21",
      "SubmittedBy": "[email protected]",
      "UpdatedTime": "2026-05-21 11:00:00",
      "Documentation": "Consolidated weekly replenishment order.",
      "ContactName": "John Doe",
      "ContactEmail": "[email protected]",
      "ShipMethod": "land",
      "Address1": "123 Any St",
      "Address2": "Suite 400",
      "Address3": "Dock 7",
      "City": "Chicago",
      "State": "IL",
      "PostalCode": "60601",
      "Country": "US",
      "LineItems": [
        {
          "ItemCode": "938939",
          "ItemDescription": "Widget Small",
          "Quantity": 480,
          "QuantityUnadjusted": 462,
          "Cost": 10.0,
          "LineComment": "Rounded up to pallet quantity",
          "ShippingLocation": "ACME",
          "ReceivingLocation": "CHI01",
          "OrderNumber": "PO-2026-004821",
          "OrderType": "REPLEN",
          "ReceiveDate": "2026-06-21",
          "DeliveryDate": "2026-06-19",
          "LineNumber": "1",
          "BuyerCode": "Jack",
          "ApprovalCode": "U",
          "ApprovedBy": "finley",
          "PreOrderCarryCost": 112.4,
          "DaysOverLeadTime": 0.0,
          "Weight": 1.85,
          "Volume": 0.02,
          "ConversionFactor": 12.0,
          "BundleCode": 3,
          "ExtendedData": {                // optional - line item level
            "CustomerInternalId": "REQ-88230",
            "CostCentre": "MTN-01"
          }
        },
        {
          "ItemCode": "938940",
          "ItemDescription": "Widget Large",
          "Quantity": 1200,
          "QuantityUnadjusted": 1200,
          "Cost": 100.0,
          "LineComment": "Standard pack quantity",
          "ShippingLocation": "ACME",
          "ReceivingLocation": "CHI01",
          "OrderNumber": "PO-2026-004821",
          "OrderType": "REPLEN",
          "ReceiveDate": "2026-06-21",
          "DeliveryDate": "2026-06-20",
          "LineNumber": "2",
          "BuyerCode": "Jack",
          "ApprovalCode": "U",
          "ApprovedBy": "finley",
          "PreOrderCarryCost": 41.0,
          "DaysOverLeadTime": 2.5,
          "Weight": 0.4,
          "Volume": 0.004,
          "ConversionFactor": 24.0,
          "BundleCode": 4,
          "ExtendedData": {                // optional - line item level
            "CustomerInternalId": "REQ-88231",
            "CostCentre": "MTN-02"
          }
        }
      ],
      "ExtendedData": {                    // optional - order header level
        "SourceSystemOrderId": "ERP-55120"
      }
    }
  ]
}
{
  "DataType": "transfer_order",
  "Data": [
    {
      "TrackingNumber": 4822,
      "ShippingLocation": "DC-EAST",
      "ReceivingLocation": "CHI01",
      "SenderName": "East Regional Distribution Center",
      "ReleaseDate": "2026-05-19",
      "RequestedDeliveryDate": "2026-05-26",
      "SubmittedBy": "[email protected]",
      "UpdatedTime": "2026-05-19 09:14:02",
      "Documentation": "Rebalancing shipment.",
      "ContactName": "Marc Oliveira",
      "ContactEmail": "[email protected]",
      "ShipMethod": "LTL",
      "Address1": "77 Dockside Road",
      "Address2": "Suite 12",
      "Address3": "Bay 3",
      "City": "Harrisburg",
      "State": "PA",
      "PostalCode": "17101",
      "Country": "US",
      "LineItems": [
        {
          "ItemCode": "938939",
          "ItemDescription": "Widget Small",
          "Quantity": 96,
          "QuantityUnadjusted": 88,
          "Cost": 10.0,
          "LineComment": "Pallet transfer",
          "ShippingLocation": "DC-EAST",
          "ReceivingLocation": "CHI01",
          "OrderNumber": "TO-2026-004822",
          "OrderType": "REBAL",
          "ReceiveDate": "2026-05-26",
          "DeliveryDate": "2026-05-24",
          "LineNumber": "1",
          "BuyerCode": "Jack",
          "ApprovalCode": "U",
          "ApprovedBy": "System",
          "PreOrderCarryCost": 22.6,
          "DaysOverLeadTime": 0.0,
          "Weight": 1.85,
          "Volume": 0.02,
          "ConversionFactor": 12.0,
          "BundleCode": 2,
          "ExtendedData": {                // optional - line item level
            "CustomerInternalId": "REQ-88240",
            "CostCentre": "MTN-02"
          }
        }
      ],
      "ExtendedData": {                    // optional - order header level
        "SourceSystemOrderId": "ERP-55121"
      }
    }
  ]
}
{
  "DataType": "work_order",
  "Data": [
    {
      "TrackingNumber": 4823,
      "ShippingLocation": "PLANT-02",
      "ReceivingLocation": "CHI01",
      "VendorName": "Midwest Assembly Plant",
      "ReleaseDate": "2026-05-22",
      "RequestedDeliveryDate": "2026-06-12",
      "SubmittedBy": "System",
      "UpdatedTime": "2026-05-22 07:30:41",
      "Documentation": "Build to replenish safety stock.",
      "ContactName": "Priya Raman",
      "ContactEmail": "[email protected]",
      "ShipMethod": "INTERNAL",
      "Address1": "4 Foundry Lane",
      "Address2": "Unit 5",
      "Address3": "Line 2",
      "City": "Toledo",
      "State": "OH",
      "PostalCode": "43604",
      "Country": "US",
      "LineItems": [
        {
          "ItemCode": "ASM-7701",
          "ItemDescription": "Hub assembly, front",
          "Quantity": 150,
          "QuantityUnadjusted": 150,
          "Cost": 62.1,
          "LineComment": "Requires 938939 sub-assembly",
          "ShippingLocation": "PLANT-02",
          "ReceivingLocation": "CHI01",
          "OrderNumber": "WO-2026-004823",
          "OrderType": "BUILD",
          "ReceiveDate": "2026-06-12",
          "DeliveryDate": "2026-06-10",
          "LineNumber": "1",
          "BuyerCode": "P07",
          "ApprovalCode": "U",
          "ApprovedBy": "System",
          "PreOrderCarryCost": 305.9,
          "DaysOverLeadTime": 1.0,
          "Weight": 6.2,
          "Volume": 0.05,
          "ConversionFactor": 1.0,
          "BundleCode": 1,
          "ExtendedData": {                // optional - line item level
            "CustomerInternalId": "REQ-88241",
            "CostCentre": "MTN-03"
          }
        }
      ],
      "ExtendedData": {                    // optional - order header level
        "SourceSystemOrderId": "ERP-55122"
      }
    }
  ]
}

Inventory Policy (InventoryPolicyUpdate)

A webhook is issued when GAINS detects that one of the attributes below has changed since the previous run. The change can come from a user action in the GAINS UI or from a system process.

The attributes watched for a change are:

  • AdjustedOrq
  • MinimumOrq
  • IncrementalOrq
  • Rsq
  • RsqOverride
  • CalcSs
  • AdjustedSs
  • SsOverride
  • ServiceLevel
  • ServiceLevelMinimum
  • InventoryClass
  • StockIndicator
  • LeadTime
{
  "DataType": "inventory_policy",
  "Data": [
    {
      "ItemCode": "938939",
      "ItemDescription": "Widget Small",
      "Locations": [
        {
          "LocationCode": "CHI01",
          "LocationName": "Chicago Central Warehouse",
          "InventoryPolicy": {
            "AdjustedOrq": 480,
            "MinimumOrq": 120,
            "IncrementalOrq": 12,
            "CalcOrq": 462,
            "Rsq": 60,
            "RsqOverride": 55,
            "CalcSs": 210,
            "AdjustedSs": 216,
            "SsOverride": 200,
            "ServiceLevel": 0.975,
            "ServiceLevelMinimum": 0.95,
            "InventoryClass": "A",
            "StockIndicator": "S",
            "LeadTime": 3.5,
            "ForecastError": 42.18,
            "AvgLTTotalDemand": 385
          },
          "ExtendedData": {                // optional - location level
            "PlannerCode": "PL-07"
          },
          "EventOccurredAt": "2026-05-21 02:15:44"
        },
        {
          "LocationCode": "DC-EAST",
          "LocationName": "East Regional Distribution Center",
          "InventoryPolicy": {
            "AdjustedOrq": 240,
            "MinimumOrq": 60,
            "IncrementalOrq": 12,
            "CalcOrq": 233,
            "Rsq": 24,
            "RsqOverride": 30,
            "CalcSs": 96,
            "AdjustedSs": 96,
            "SsOverride": 90,
            "ServiceLevel": 0.96,
            "ServiceLevelMinimum": 0.95,
            "InventoryClass": "B",
            "StockIndicator": "S",
            "LeadTime": 2.0,
            "ForecastError": 18.05,
            "AvgLTTotalDemand": 140
          },
          "ExtendedData": {                // optional - location level
            "PlannerCode": "PL-12"
          },
          "EventOccurredAt": "2026-05-21 02:15:44"
        }
      ],
      "ExtendedData": {                    // optional - item level
        "ProductFamily": "BRAKING"
      }
    }
  ]
}

Forecast (ForecastWeeklyUpdate, ForecastMonthlyUpdate)

A webhook is issued when GAINS detects that the forecast has changed since the previous run. The change can come from a user action in the GAINS UI or from a system process.

The weekly and monthly triggers publish the same structure under the same DataType - only the bucket boundaries differ. If you subscribe to both, distinguish them by the length of the period each bucket covers.

{
  "DataType": "forecast",
  "Data": [
    {
      "ItemCode": "938939",
      "Locations": [
        {
          "LocationCode": "CHI01",
          "InventoryClass": "A",
          "ForecastBuckets": [
            {
              "ForecastPeriodNumber": 1,
              "ForecastPeriodStart": "2026-05-25",
              "ForecastPeriodEnd": "2026-05-31",
              "ForecastQuantityTotal": 118,
              "ForecastQuantityTotalRaw": 112
            },
            {
              "ForecastPeriodNumber": 2,
              "ForecastPeriodStart": "2026-06-01",
              "ForecastPeriodEnd": "2026-06-07",
              "ForecastQuantityTotal": 124,
              "ForecastQuantityTotalRaw": 119
            },
            {
              "ForecastPeriodNumber": 3,
              "ForecastPeriodStart": "2026-06-08",
              "ForecastPeriodEnd": "2026-06-14",
              "ForecastQuantityTotal": 131,
              "ForecastQuantityTotalRaw": 126
            }
          ],
          "ExtendedData": {                // optional - location level
            "PlannerCode": "PL-07"
          },
          "EventOccurredAt": "2026-05-21 03:05:12"
        }
      ],
      "ExtendedData": {                    // optional - item level
        "ProductFamily": "BRAKING"
      }
    }
  ]
}
{
  "DataType": "forecast",
  "Data": [
    {
      "ItemCode": "938939",
      "Locations": [
        {
          "LocationCode": "CHI01",
          "InventoryClass": "A",
          "ForecastBuckets": [
            {
              "ForecastPeriodNumber": 1,
              "ForecastPeriodStart": "2026-06-01",
              "ForecastPeriodEnd": "2026-06-30",
              "ForecastQuantityTotal": 540,
              "ForecastQuantityTotalRaw": 521
            },
            {
              "ForecastPeriodNumber": 2,
              "ForecastPeriodStart": "2026-07-01",
              "ForecastPeriodEnd": "2026-07-31",
              "ForecastQuantityTotal": 505,
              "ForecastQuantityTotalRaw": 498
            }
          ],
          "ExtendedData": {                // optional - location level
            "PlannerCode": "PL-12"
          },
          "EventOccurredAt": "2026-05-21 03:20:47"
        }
      ],
      "ExtendedData": {                    // optional - item level
        "ProductFamily": "BRAKING"
      }
    }
  ]
}

Task Status (TaskStatusUpdate)

Issued each time an inbound integration task you submitted changes status. Batch carries the record counts; Accepted, AcceptedWithExclusions and Rejected remain null until the task reaches a final state, at which point any refused records are listed. RejectedRecords[].Record is the record as GAINS Connect received it, and Reasons explains why it was refused. These are records GAINS refused while loading them - a record that fails the endpoint's own validation is refused at submit time with a 422 response and never becomes a task at all.

Status is one of Received, Queued, PendingReview, Processing, Completed, RejectedFailedProcessing, RejectedNotApproved, Cancelled or Failed.

{
  "DataType": "task",
  "Data": [
    {
      "TaskId": "8f3c1d02-5a77-4e61-9b0e-2d41c7a9f5b3",
      "TaskType": "InboundDemandTrans",
      "Status": "Processing",
      "StatusMessage": "[InboundDemandTrans] is currently being processed in to GAINS.",
      "Batch": {
        "TotalRecords": 1000,
        "Accepted": null,
        "AcceptedWithExclusions": null,
        "Rejected": null,
        "RejectedRecords": []
      },
      "StatusUpdatedOn": "2026-05-21 11:45:02",
      "CreatedOn": "2026-05-21 11:44:38",
      "CreatedBy": "[email protected]",
      "UpdatedBy": "System",
      "Comments": "Queued behind the nightly load."
    }
  ]
}
{
  "DataType": "task",
  "Data": [
    {
      "TaskId": "8f3c1d02-5a77-4e61-9b0e-2d41c7a9f5b3",
      "TaskType": "InboundDemandTrans",
      "Status": "Completed",
      "StatusMessage": "Processing completed. Total objects = 1000. Accepted = 999. Accepted (with exclusions) = 0. Rejected = 1.",
      "Batch": {
        "TotalRecords": 1000,
        "Accepted": 999,
        "AcceptedWithExclusions": 0,
        "Rejected": 1,
        "RejectedRecords": [
          {
            "Record": {
              "IdealShipFromLocationCode": "CHI01",
              "ActualShipFromLocationCode": "CHI01",
              "CustomerRequestDate": "2026-05-28",
              "OrderEntryDate": "2026-05-21",
              "OrderNumber": "SO-2026-77431",
              "CustomerShipToCode": "SHIP-4410",
              "CustomerCode": "CUST-2048",
              "CustomerCodeDescription": "Northwind Distributors",
              "OrderFulfillCost": 120,
              "OrderRevenueCost": 380,
              "Channel": "WEB",
              "LotNumber": "LOT-5521",
              "SalesOrderComments": "Expedite if stock allows.",
              "LineItems": [
                {
                  "ItemCode": "938939",
                  "IdealShipFromLocationCode": "CHI01",
                  "ActualShipFromLocationCode": "CHI01",
                  "CustomerRequestDate": "2026-05-28",
                  "OrderFulfillCost": 120,
                  "OrderRevenueCost": 380,
                  "LotNumber": "LOT-5521",
                  "ItemDescription": "Widget Small",
                  "PromiseDate": "2026-05-29",
                  "OrderReleaseDate": "2026-05-22",
                  "OrderShipDate": "2026-05-27",
                  "RequestedQuantity": -12,
                  "ShippedQuantity": -4,
                  "SalesPrice": 24.5,
                  "LineNumber": "1",
                  "LineComment": "Return correction",
                  "BtoKitItemCode": "KIT-100",
                  "BtoKitLocationCode": "CHI01",
                  "OrderType": 1,
                  "ActualItemNumber": "938939",
                  "Status": "S",
                  "ExcludeFlag": false,
                  "ExtendedData": {
                    "CustomerInternalId": "REQ-88250"
                  }
                }
              ],
              "ExtendedData": {
                "SourceSystemOrderId": "ERP-55130"
              }
            },
            "Reasons": [
              {
                "Reason": "RequestedQuantity is negative"
              },
              {
                "Reason": "ShippedQuantity is negative"
              }
            ]
          }
        ]
      },
      "StatusUpdatedOn": "2026-05-21 11:47:31",
      "CreatedOn": "2026-05-21 11:44:38",
      "CreatedBy": "[email protected]",
      "UpdatedBy": "System",
      "Comments": "One record refused; see RejectedRecords."
    }
  ]
}

Webhook Timeouts

Please note that webhooks are designed to live for 90 seconds. This means:

  • Request Timeout: If your server does not respond within 90 seconds, the webhook delivery attempt will be considered a failure
  • Retries: If a delivery attempt fails (e.g., your server returns a non-2xx status code or times out), our system will retry the delivery according to a retry process. All retries will also adhere to the 90-second lifespan

Resending a Webhook

You may request a re-transmission of a webhook based on existing webhook Url and Authorization attributes. Re-sending a webhook can be done on the basis of an entire payload WebhookId or partial payload based on a TrackingNumber (i.e. order object) contained in a webhook.

Note:

  • Webhook re-send requests will be respected even if the trigger for that type of webhook is disabled in webhook settings
  • Please be aware that re-sending webhooks can result in duplicate data. Make sure your systems are equipped to manage potential duplicates effectively

Did this page help you?