Applies to: Transfer Orders API | Webhook Subscriptions API
Learn how to handle webhooks emitted by the Transfer Orders API on an inventory transfer event.
The Transfer Orders API provides three webhook events that fire when transfer orders change:
transfer_order.created
- Triggered when a new transfer order is created.transfer_order.updated
- Triggered when a transfer order is modified (items are added or removed, quantities are changed, statuses are updated, or items are received).transfer_order.deleted
- Triggered when a draft transfer order is deleted.
For the Fleet Foot Running Co. multi-location inventory management system, these webhooks enable:
- Immediate notifications to source location managers when another store requests their inventory.
- Real-time updates when transfer quantities or items change.
- Alerts when transfers are canceled or completed.
- Automatic synchronization across all store management systems.
For more information about how to implement Square API webhooks, see Square Webhooks.
Fleet Foot Running Co. operates multiple store locations and managers need real-time visibility when other stores request inventory transfers from their location. By implementing transfer order webhooks, your application can automatically notify managers when transfer orders affecting their inventory are created, modified, or canceled—ensuring that they stay informed about stock movements across the entire retail network.
To receive transfer order notifications across all Fleet Foot Running Co. locations, create a webhook subscription that monitors all three transfer order events:
Create webhook subscription
Important
Store the signature_key
securely. You need it to verify that webhook notifications are genuinely from Square.
When the Olympia store creates a transfer order requesting New Balance FuelCell shoes from Tacoma, the webhook fires immediately.
{ "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.created", "event_id": "evt_123e4567-e89b-12d3-a456-426614174000", "created_at": "2025-10-09T10:15:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "DRAFT", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T10:15:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "created_by_team_member_id": "tm_olympia_manager", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3" } ], "version": 1 } } } }
The webhook fires for various update scenarios:
Scenario 1: Adding items to a draft
{ "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_223e4567-e89b-12d3-a456-426614174001", "created_at": "2025-10-09T10:30:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "DRAFT", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T10:30:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "created_by_team_member_id": "tm_olympia_manager", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4" } ], "version": 2 } } } }
Scenario 2: Starting the transfer
{ "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_323e4567-e89b-12d3-a456-426614174002", "created_at": "2025-10-09T11:00:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "STARTED", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-09T11:00:00.000Z", "started_at": "2025-10-09T11:00:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "tracking_number": "FLEET-042", "created_by_team_member_id": "tm_olympia_manager", "started_by_team_member_id": "tm_tacoma_staff", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5", "quantity_pending": "5", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3", "quantity_pending": "3", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4", "quantity_pending": "4", "quantity_received": "0", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 3 } } } }
Scenario 3: Receiving items (partial receipt)
{ "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.updated", "event_id": "evt_423e4567-e89b-12d3-a456-426614174003", "created_at": "2025-10-10T09:30:00.000Z", "data": { "type": "transfer_order", "id": "ZXOLSDDQKOCBMTWY", "object": { "transfer_order": { "id": "ZXOLSDDQKOCBMTWY", "source_location_id": "EWVV7AYQC45SS", "destination_location_id": "90A9W5RRYD2GQ", "status": "PARTIALLY_RECEIVED", "created_at": "2025-10-09T10:15:00.000Z", "updated_at": "2025-10-10T09:30:00.000Z", "started_at": "2025-10-09T11:00:00.000Z", "expected_at": "2025-10-10T09:00:00Z", "tracking_number": "FLEET-042", "created_by_team_member_id": "tm_olympia_manager", "started_by_team_member_id": "tm_tacoma_staff", "received_by_team_member_id": "tm_olympia_receiver", "line_items": [ { "transfer_order_line_uid": "HVU4OIW64ZMKPGTA", "item_variation_id": "XPBDUOG3VQBRASADVRSOYS67", "quantity_ordered": "5", "quantity_pending": "0", "quantity_received": "4", "quantity_damaged": "1", "quantity_canceled": "0" }, { "transfer_order_line_uid": "JKL5OIW64ZMKPGTB", "item_variation_id": "R6C5CP6JXBZMA22FSXYVUC5W", "quantity_ordered": "3", "quantity_pending": "0", "quantity_received": "3", "quantity_damaged": "0", "quantity_canceled": "0" }, { "transfer_order_line_uid": "MNO6OIW64ZMKPGTC", "item_variation_id": "J4H4PL3UGRAWCUDW3JS73LT6", "quantity_ordered": "4", "quantity_pending": "2", "quantity_received": "2", "quantity_damaged": "0", "quantity_canceled": "0" } ], "version": 4 } } } }
When a draft transfer order is deleted.
{ "merchant_id": "FLEET_FOOT_MERCHANT_ID", "type": "transfer_order.deleted", "event_id": "evt_523e4567-e89b-12d3-a456-426614174004", "created_at": "2025-10-09T10:45:00.000Z", "data": { "type": "transfer_order", "id": "ABC123DELETEEXAMPLE", "deleted": true } }
Your webhook endpoint should process notifications and alert the appropriate store managers.
// Example webhook handler for Fleet Foot Running Co. app.post('/webhooks/transfer-orders', async (req, res) => { // Verify the webhook signature if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } const event = req.body; const transferOrder = event.data.object?.transfer_order; try { switch(event.type) { case 'transfer_order.created': // Notify source location manager about new transfer request await notifySourceLocation(transferOrder); break; case 'transfer_order.updated': // Determine what changed and notify relevant parties await processTransferUpdate(transferOrder); break; case 'transfer_order.deleted': // Notify that transfer was canceled await notifyCancellation(event.data.id); break; } // Acknowledge receipt res.status(200).send('OK'); } catch (error) { console.error('Webhook processing error:', error); res.status(500).send('Internal Server Error'); } }); async function notifySourceLocation(transferOrder) { const sourceLocationId = transferOrder.source_location_id; const destinationLocationId = transferOrder.destination_location_id; // Look up location details const sourceLocation = await getLocationDetails(sourceLocationId); const destinationLocation = await getLocationDetails(destinationLocationId); // Get item details for the notification const itemDetails = await getItemDetails(transferOrder.line_items); // Send notification to source location manager const notification = { type: 'TRANSFER_REQUEST', priority: transferOrder.status === 'STARTED' ? 'HIGH' : 'MEDIUM', message: `${destinationLocation.name} has requested a transfer of ${itemDetails.totalItems} items`, details: { transferOrderId: transferOrder.id, requestingStore: destinationLocation.name, status: transferOrder.status, expectedDate: transferOrder.expected_at, items: itemDetails.breakdown } }; await sendManagerNotification(sourceLocation.manager_email, notification); } async function processTransferUpdate(transferOrder) { // Check what changed by examining the status if (transferOrder.status === 'STARTED') { // Transfer has been started - notify destination await notifyDestinationLocation(transferOrder, 'TRANSFER_STARTED'); } else if (transferOrder.status === 'PARTIALLY_RECEIVED') { // Some items received - notify source about discrepancies await checkForDiscrepancies(transferOrder); } else if (transferOrder.status === 'COMPLETED') { // Transfer complete - notify both locations await notifyTransferComplete(transferOrder); } else if (transferOrder.status === 'CANCELED') { // Transfer canceled - notify both locations await notifyTransferCanceled(transferOrder); } }
Transfer order webhooks might be delivered multiple times. Use the event_id
to ensure that you process each event only once.
async function processWebhook(event) { // Check if we've already processed this event if (await hasProcessedEvent(event.event_id)) { return { status: 'already_processed' }; } // Process the event await handleTransferOrderEvent(event); // Mark as processed await markEventProcessed(event.event_id); }
Respond to webhook notifications within one minute to avoid receiving a retry request from Square.
app.post('/webhooks/transfer-orders', async (req, res) => { // Immediately acknowledge receipt res.status(200).send('OK'); // Process asynchronously processWebhookAsync(req.body); });
Always verify webhook signatures to ensure that notifications are from Square.
function verifyWebhookSignature(request) { const signature = request.headers['x-square-hmacsha256-signature']; const body = JSON.stringify(request.body); const hash = crypto .createHmac('sha256', webhookSignatureKey) .update(request.headers['x-square-request-timestamp'] + body) .digest('base64'); return signature === hash; }
Route notifications to the appropriate store managers based on their role.
async function routeNotification(transferOrder) { const notifications = []; // Always notify source location when items are requested if (transferOrder.source_location_id) { notifications.push({ locationId: transferOrder.source_location_id, type: 'OUTBOUND_TRANSFER', priority: 'HIGH' }); } // Notify destination for status updates if (transferOrder.status !== 'DRAFT') { notifications.push({ locationId: transferOrder.destination_location_id, type: 'INBOUND_TRANSFER', priority: 'MEDIUM' }); } return notifications; }
Square provides a webhook tester in the Developer Console. To test your Fleet Foot Running Co. notification system:
- Navigate to the Webhooks section in the Square Developer Console.
- Select your transfer order webhook subscription.
- Choose Send test event and choose the event type.
- Verify that your endpoint receives the notification and that managers get alerts.
You can also trigger real webhook events in the Square Sandbox environment by creating, updating, and deleting test transfer orders between your test locations.
The following shows a typical sequence of webhook events for a complete transfer between Fleet Foot Running Co. stores:
- 10:15 AM - Olympia creates a draft transfer →
transfer_order.created
(the Tacoma manager is notified). - 10:30 AM - Olympia adds more items →
transfer_order.updated
(the Tacoma manager is notified of the changes). - 11:00 AM - Tacoma starts the transfer →
transfer_order.updated
(the Olympia manager is notified of the shipment). - Next day 9:30 AM - Olympia receives items →
transfer_order.updated
(both managers are notified of the completion).
This webhook-driven notification system ensures that all Fleet Foot Running Co. locations stay synchronized and that managers have real-time visibility into inventory movements affecting their stores.