Ship-to-Store Integration

About Ship to Store (STS)

Using local retailers as Ship-to-Store pickup sites, service points, or in-store returns is becoming a key need for every brand. Locally makes this easy by leveraging your existing, managed dealer network. We enable each dealer who is participating in Buy it Locally to accept shipments for prepaid customers. The process is seamless, shipping fees are minimal as compared to residential shipments, and is supportive of your wholesale channel.

Configuring Your Ship to Store Profile

If you don’t already have a Locally brand account, please contact your Locally representative about getting your company account set up. Once your account is set up you can manage basic settings for your Ship to Store account at https://www.locally.com/station/panel/shipping_configurations.

The following fields are editable in this configuration panel:

"Available to Sell" Feed

You will need to provide and host a single CSV document indicating which of your company’s UPCs are available to sell. This file has a very simple format, containing only 5 fields: UPC, Quantity, Percent Revenue Retained, Shipping Cost and Country (in that order). Locally will scan this feed at least once per hour. The UPC and Quantity field are self-explanatory. Percent Retained is the percent of total revenue from the customer that your company will retain when a purchase of this UPC is made. Shipping Cost is the amount that the customer will be charged for having the item shipped to the store. See the “Billing Considerations” section below for more info on these fields.

Note: Locally recommends not adding any shipping charges to be passed on to the customer. Only large/heavy items that would otherwise be more expensive to ship directly to a customer’s residence should have an additional shipping fee. By default the shipping fee for the customer is $0.

Feed format sample:

upcqtypercent revenue retainedshipping costcountry
7276022591781700US
60528457453927025.50US
736745040314100580US

Alternatively, you can submit revenue splits by defining the dollar amount the retailer should receive on the order, which we call “monetary splits.” To use this method, replace the percent revenue retained column with a column titled split_flat and include the amount of revenue the retailer should retain on an order for that product. The brand is credited for the leftover amount.

Alternate feed format sample:

upcqtysplit_flatshipping costcountry
72760225917812500US
605284574539250025.50US
7367450403141001000U

Availability of your products across all of your retailers in the Locally ecosystem may take a bit longer than one hour to propagate. But changes will be reflected at all retailer locations within no more than 3 hours of any detected change.

It is your responsibility to keep this feed updated. If the feed is not available for Locally to scan, we will temporarily disable Ship-to-Store functionality for your brand. We will continue to scan for the feed and will re-enable functionality when the feed has been restored.

Webhook URL for Order Updates

You will need to provide Locally with a webhook URL endpoint. This URL can be changed at any time via the STS settings configuration panel. Locally will send a payload containing the cart object (see API documentation below) to this endpoint whenever a new order is created or has been updated. It will be your responsibility to consume this payload and act accordingly in order to fulfill the request in a timely manner. See the “Buy it Locally: Real-time Carts API” section below for more information on this topic.

Default Revenue Percent Retained

As mentioned earlier, your “available to sell” feed will inform Locally about the percentage of customer revenue that will be retained by your company. However, you can set a default percentage in the STS configuration panel to handle cases where either a) there is no percent defined for a UPC in the “available to sell” feed. Or b) no percentages have been defined in the feed at all.

Real-time Carts API

The Real-time Carts API is your way to request up-to-the-minute information on all orders you have access to, and how to update an existing order’s status that resides within the Locally system.

API Key

You will need an API Key to access your CartAPI endpoint. See Generate An API Key for more info.

Once you have your API key, you will be able to access our /carts API endpoints.

GET /carts/{page} endpoint

The way a request is built is by adding “Locally-API-Token” to the header of your request, and the value of that will be the API key you generated on our platform.

To access the list of all the carts, access:

GET https://www.locally.com/api/v2/carts

You will need to pass along your API Key in the header of the request. For example, in cURL:

curl -X GET <https://www.locally.com/api/v2/carts> -H 'Locally-API-Token: ea432b8a353ef459be46f147b33ae37f0cae2337'

This will return the list of all carts your account has access to, ordered by most recent first, and broken up via 50 per page. First page can be accessed via just /api/v2/carts or /api/v2/carts/0

Sample, abbreviated payload:

{  
    "status": true,  
    "properties": {  
        "date_time": "2020-03-29 03:31:25",  
        "current_page": 0,  
        "total_pages": 6  
    },  
    "carts": [{  
        "hash": "O3V36",  
        "type": "SHIP",  
        "source": "From Locally.com",  
        "n_items": 1,  
        "ordered_at": "2020-03-28 11:34:21",  
        "updated_at": "2020-03-28 12:41:04",  
        "delivery_status": "Delivery fee has been reconciled",
        "pickup_window": "2020-03-28 18:00:00",
        "items": [{  
            "currency": "USD",  
            "total": 110.85,  
            "status": "CONFIRMED",  
            "sts_status": "",  
            "qty": 1,  
            "product_id": 156732,  
            "order_resolution": "SUCCESS",  
            "upc": "190340561721",
            "alias": [
                  "D1447020122-00003"
                  ]
        }]  
    	}, {  
        "hash": "4RT36",  
        "type": "ORDER",  
        "source": "From Locally.com",  
        "n_items": 2,  
        "ordered_at": "2020-03-27 15:34:21",  
        "updated_at": "2020-03-27 17:41:04",  
        "delivery_status": "Delivery fee has been reconciled",
        "pickup_window": "2020-03-28 18:00:00",
        "items": [{  
            "currency": "USD",  
            "total": 54.85,  
            "status": "CONFIRMED",  
            "sts_status": "CONFIRMED",  
            "qty": 1,  
            "product_id": 356732,  
            "upc": "190340561721", 
            "alias": [],
            "order_resolution": "SUCCESS"  
        }, {  
            "currency": "USD",  
            "total": 165.36,  
            "status": "CONFIRMED",  
            "sts_status": "CONFIRMED",  
            "qty": 1,  
            "product_id": 254385,  
            "upc": "190340561726",  
            "alias": [],
            "order_resolution": "SUCCESS"  
        }]  
    }]  
}

Definition of the response payload:

FieldDescription
statusBoolean. true if request was a success, false if not.
messageOnly sent if status was false, gives the reason for the false status.
properties.date_timeThe datetime of this request.
properties.current_pageThe current page you are on. Pagination starts at page 0 (zero).
properties.total_pagesTotal number of pages. If total pages is 5, then pages 0, 1, 2, 3, 4 exist.
cartsArray of carts.
carts[].hashThe unique hash for the order. For example E3GKY8.
carts[].typeHOLD for a reservation for in-store payment.
ORDER for a payment for in-store pickup.
SHIP for a Ship-to-Store order (This is a special case for select brands only. This transaction type is NOT available for POS vendor or retailer integrations).
carts[].n_itemsNumber of items in the order.
carts[].sourceDomain where the order originated from. “From Locally.com”, “From brand-site.com”, etc.
carts[].ordered_atThe datetime of when the order was placed.
carts[].updated_atThe datetime of when the order has changed last.
carts[].delivery_statusIf the cart is a Same-Day Delivery, this is its current status. PushCart does not support Same-Day Delivery at this time, so this will be blank.
carts[].pickup_windowThe datetime string of the selected pickup window slot time or empty string.
carts[].itemsBreakdown of items in carts statuses and basic information. Please note that there can be more than one item per cart.
carts[].items[].currencyThe currency the order is in. Adheres to ISO 4217
carts[].items[].totalThe total amount for the item. Includes tax if this is a "Buy Online, Pickup in Store (BOPIS)" order.
carts[].items[].statusThe status of the item:

Ordered : The BOPIS or ROPIS request has been placed and has not been resolved by the store.
Confirmed : The store has confirmed that the item is in stock and is being held for the shopper.
Canceled : The store has rejected the BOPIS or ROPIS request.
Charge_Failed : There was an issue charging the shopper’s credit card on a BOPIS order and payment was not captured. This is rare but should be accounted for. The store should treat these like reservation requests and collect payment when the shopper arrives.
carts[].items[].sts_statusThis is for Ship-to-Store orders only. If the store is not participating in Ship-to-Store, this will always read “N/A”.
carts[].items[].qtyQuantity of this item.
carts[].items[].product_idThe Locally Product ID of the item.
carts[].items[].order_resolutionThe order resolution, SUCCESS, DECLINED, etc. This gives more detail as to why/how the item status is what it is. The full list of order resolution statuses can be found later in this document under Other API Functionality: Order Resolution.
carts[].items[].upcThe UPC/EAN of the item.
carts[].items[].alias Array of UPC/EAN aliases or empty array in case of no aliases

Reference Doc: /carts/{page}

GET /cart/{cart_hash} endpoint

This will give you all the data for this specific cart. Your company must have access to this cart in order to retrieve its data.

To access this single cart’s information, use this endpoint:

GET https://www.locally.com/api/v2/cart/{cart_hash}

You will need to pass along your API Key in the header of the request. For example, in cURL:

curl -X GET <https://www.locally.com/api/v2/cart/PDQE89> -H 'Locally-API-Token: ea432b8a353ef459be46f147b33ae37f0cae2337'

Sample, abbreviated payload:

{  
    "status": true,  
    "properties": {  
        "date_time": "2021-02-02 12:48:03",  
        "type": "ORDER",  
        "hash": "PDQE89",
       "pickup_window": "2021-02-06 18:00:00",
        "updated_at": "2021-01-22 13:11:57",  
        "delivery_status": "",  
        "invoice": "https://www.locally.com/order/PDQE89/SJQWNEFK3966?print=1"  
    },  
    "customer": {  
        "first_name": "John",  
        "last_name": "smith",  
        "email": "[email protected]",  
        "phone": "+18054031225"  
    },  
    "store": {  
        "name": "Hypercat Cycleworks",  
        "address": "555 University Ave",  
        "phone": "222-333-4444",  
        "city": "Ventura",  
        "state": "CA",  
        "zip": "93003",  
        "store_id": 128943,  
        "vendor_id": 0  
    },  
    "items": [  
        {  
            "status": "Confirmed",  
            "sts_status": "Processing",  
            "product_id": 531197,  
            "qty": 1,  
            "brand": "Tron Bicycles",  
            "upc": "811950030719",
            "alias": [],
            "name": "The E-bike",  
            "attrib_1": "Medium",  
            "attrib_2": "",  
            "currency": "USD",  
            "msrp": "2,099.99",  
            "tax": "162.75",  
            "tax_remitted": true,  
            "commission": "73.50",  
            "total": "2,262.74",  
            "disbursement": "1,262.74",  
            "order_resolution": "",  
            "resolution_notes": "",  
            "messages": [  
                {  
                    "sender": "locally",  
                    "description": "Please accept or decline this order. If you do not reply within 2 hours, we'll auto accept it.",  
                    "created_at": "2021-01-17 15:04:06"  
                },  
                {  
                    "sender": "locally",  
                    "description": "You approved this item for the Ship-to-Store order.",  
                    "created_at": "2021-01-17 15:11:53"  
                },  
                {  
                    "sender": "locally",  
                    "description": "The E-bike has shipped.",  
                    "created_at": "2021-01-18 07:03:20"  
                },  
                {  
                    "sender": "customer",  
                    "description": "Hi, what’s the lead time on delivery?\r\n\r\nJohn",  
                    "created_at": "2021-01-18 12:23:14"  
                },  
                {  
                    "sender": "locally",  
                    "description": "Tron Bicycles is processing this item, you will be notified when it ships.",  
                    "created_at": "2021-01-18 12:32:42"  
                },  
                {  
                    "sender": "retailer",  
                    "description": "The bike is in process and will be shipped in the next day or two.  Average ship time is 4 days. Puts delivery, built and ready for pick up right about 1 week. If it is ready sooner you will be notified that the bike is ready to pick up.  These are fun bikes and we have had great success with them.  Good choice",  
                    "created_at": "2021-01-18 12:37:23"  
                },  
                {  
                    "sender": "customer",  
                    "description": "Great, thanks",  
                    "created_at": "2021-01-18 12:38:30"  
                },  
                {  
                    "sender": "retailer",  
                    "description": "John - your bike is in and will be ready for pick-up today Friday between 1pm and 6pm or during the weekend 12-4.  Let me know if you have any questions.  \r\n\r\nPhil",  
                    "created_at": "2021-01-22 13:07:48"  
                },  
                {  
                    "sender": "customer",  
                    "description": "Great, I’ll come down tomorrow. See you then.",  
                    "created_at": "2021-01-22 13:11:56"  
                }  
            ]  
        }  
    ]  
}

Definition of the response payload:

FieldDescription
statusBoolean. true if request was a success, false if not.
messageOnly set if status was false, gives the reason for the false status.
properties.date_timeThe datetime of this request.
properties.typeHOLD for a reservation for in-store payment.
ORDER for a payment for in-store pickup.
SHIP for a Ship-to-Store order (This is a special case for select brands only, opt-in by the retailer is required).
properties.hashThe hash for the order. Ex. QE3QL6
properties.pickup_windowThe datetime string of the selected pickup window slot time or empty string.
properties.ordered_atThe datetime of when the order was placed.
properties.updated_atThe datetime of when the order has changed last.
properties.delivery_statusIf the cart is a Same-Day Delivery, this is its current status. PushCart does not support Same-Day Delivery at this time, so this will be blank.
properties.invoiceURL to a minimal PDF of the invoice.
customerCustomer information
customer.first_nameThe customer’s first name
customer.last_nameThe customer’s last name
customer.address_1If this is a BOPIS order or if the customer has an address on file, their billing street address. Otherwise blank.
customer.address_2If this is a BOPIS order or if the customer has an address on file, their secondary billing address. Otherwise blank.
customer.cityIf this is a BOPIS order or if the customer has an address on file, their billing city. Otherwise blank.
customer.stateIf this is a BOPIS order or if the customer has an address on file, their billing state. Otherwise blank.
customer.zipIf this is a BOPIS order or if the customer has an address on file, their billing postal code. Otherwise blank.
customer.countryIf this is a BOPIS order or if the customer has an address on file, their billing country. Otherwise blank.
customer.emailThe customers email address
customer.phoneThe customers phone number
customer.marketing_active_consentWhether a shopper has opted in to receive marketing emails from the store
storeStore information
store.nameThe store’s name
store.phoneThe store’s phone number
store.addressThe store’s street address
store.address_2If available, the store’s address 2 information
store.cityThe store’s city
store.stateThe store’s state
store.zipThe store’s postal code
store.countryThe store’s country
store.store_idThe unique Locally Store ID
store.vendor_idThe brand's unique ID for the store, if applicable
itemsArray of items in the cart
items[].statusThe status of the item:

Ordered : The BOPIS or ROPIS request has been placed and has not been resolved by the store.
Confirmed : The store has confirmed that the item is in stock and is being held for the shopper.
Canceled : The store has rejected the BOPIS or ROPIS request.
Charge_Failed : There was an issue charging the shopper’s credit card on a BOPIS order and payment was not captured. This is rare but should be accounted for. The store should treat these like reservation requests and collect payment when the shopper arrives.
sts_status: This is for Ship-to-Store orders only. If the store is not participating in Ship-to-Store, this will always read false.
items[].product_idThe Locally product ID of the item
items[].qtyQuantity of this item in the cart
items[].brandThe brand / manufacturer name of the product
items[].upcThe UPC/EAN of the item
items[].alias Array of UPC/EAN aliases or empty array in case of no aliases
items[].nameThe product name
items[].attrib_1The product’s first attribute: color, size, etc.
items[].attrib_2The product's second attribute: color, size, etc.
items[].currencyThe currency the order is in
items[].msrpThe MSRP for the item
items[].product_priceThe item price for which the shopper paid or reserved the item. This can vary from MSRP if the store uses in-store pricing
items[].product_price_less_vatIf the price includes VAT, the product price less VAT. Please note that this is separate from US and Canadian taxes and applies to VAT countries only.
items[].subtotalThe subtotal of the item.
items[].taxThe calculated tax for the item if this is a BOPIS order.
items[].tax_rateThe tax rate for the item if this is a BOPIS order. For ex., a 6.25% tax rate would appear as 6.25.
items[].tax_is_vatBoolean. Whether the tax amount is VAT or not.
items[].tax_remittedWhether or not we remitted sales tax on behalf of the store. Locally remits sales tax on behalf of stores in the US and Canada for BOPIS orders only.
items[].commissionThe commission Locally is owed for the item.
items[].stripe_feeIf this is a BOPIS order, the fee that Stripe charged for the transaction.
items[].totalThe total amount for the item. If this is a BOPIS order, this includes tax.
items[].disbursement_brandFor Ship-to-store orders only, the revenue split amount payable to the brand on this order (will display as $0 until sts_status = ‘shipped’)
items[].disbursement_retailFor Ship-to-store orders only, the revenue split amount payable to the retailer on this order (will display as $0 until sts_status = ‘picked up’)
items[].split_brandFor Ship-to-store orders only, the brand’s revenue split amount on the order
items[].split_retailerFor Ship-to-store orders only, the retailer’s revenue split amount on the order
items[].order_resolutionThe order resolution, SUCCESS, DECLINED, etc. This gives more detail as to why/how the item status is what it is. The full list of order resolution statuses can be found later in this document under Other API Functionality: Order Resolution.
items[].resolution_notesNotes regarding the order resolution if the store has included them
items[].messagesArray of messages for the item, if any exist
items[].messages[].senderWhether the message was sent by the retailer or the customer
items[].messages[].descriptionThe contents of the message
items[].messages[].created_atWhen the message was sent

POST /cart/{cart_hash}/{upc} endpoint

Use this endpoint to make updates to existing orders. See “Lifecycle of a Ship to Store Transaction” for details on the timing of these requests.

You will need to pass along your API Key in the header of the request. For example, in cURL:

curl -X POST <https://www.locally.com/api/v2/cart/O3V36/190340561726> -H 'Locally-API-Token: ea432b8a353ef459be46f147b33ae37f0cae2337' -d '{"sts_status":"processing"}'

Sample payload:

{
  "sts_status":"processing", 
  "reason":"only needed for rejected status"
}

Sample response:

{  
	"status": true,  
}

Managing Store Relationships

Activating your dealers for Ship to Store fulfillment

Any of your retailers can participate in Ship to Store transactions. However, each retailer must have a registered Locally account with “Buy it Locally” enabled. Each retailer must also approve your request for STS participation.

To get started with retailer activation, navigate to https://www.locally.com/station/panel/brand_store_relationships, select each store that you want to make a request to, and click to submit the request.

At the moment, only one request to a store can be made at a time. However, the Locally support team can assist with bulk request submission. Please inquire about this as needed.

Lifecycle of a Ship to Store Transaction

The products in your “available to sell” feed combined along with your activated retailer list will determine which stores display the “Available for Ship To Store” label on product display pages and other product locator technology throughout the Locally platform.

What follows is a description of the workflow starting with the initial customer engagement in the “Buy it Locally” system through to fulfillment (pickup) of the product from the local retailer:

API Integration; Payment, Communications and Order Handoff

PENDING BRAND:

The order is placed by the shopper and goes to the brand for confirmation or rejection. The payload to your webhook will contain the UPC, Order #, status, and order URL, as follows:

{
  "upc":"811950030719",
  "order_id":"A1234", 
  "sts_status":"confirm-store",
  "order_url":"https://www.locally.com/api/v2/cart/A1234
}

The order is now pending your approval.

You must POST to Locally’s cart API with the status confirm-brand in order to approve the customer order. For example:

{
  "sts_status":"confirm-brand"
}

To reject the order, you must submit the status reject-brand and supply a reason. For example:

{
  "sts_status":"reject-brand", 
  "reason":"Reason for rejection goes here."
}

If you reject the customer’s request, we will notify the customer and the store about the rejection. You can still communicate with the customer about this order through the Buy it Locally messaging window after an order has been rejected.

If you approve the request, the customer’s credit card will be charged immediately for the full amount. Important note: Locally.com will be the company indicated on the credit card statement.

CONFIRMED:

The shopper is notified that you’ve confirmed the order and their credit card is charged. Once you have started processing it, you must POST to Locally’s cart API with the status processing. For example:

{
  "sts_status":"processing"
}

Additionally, you can skip the “brand confirmed” step and move a new order directly to “processing.”

PROCESSING:

Customer is now notified that their order is being processed. The customer and store see a system message indicating that the order is in processing.

When the order has shipped, you must POST to Locally’s cart API with the status shipped. You can optionally provide a message that will be seen by the customer and retailer. For example:

{
  "sts_status":"shipped",
  "message":"New Item is on the way to the store. Tracking Number: 9400100000000000000000; Carrier: USPS"
}

The customer and the store will be notified about this in their Buy it Locally messaging window (and via email/SMS).

SHIPPED:

Customer is now notified that their order has shipped to the store. At this point, the brand’s revenue split for this order becomes payable and is applied to your statement.

READY FOR PICKUP:

When the item arrives at the store, it’s the store’s job to alert the customer when the item is ready by changing the order status to “Ready for Pickup.” This is completed via the Order Screen UI all retailers have access to.

PICKED UP:

Once the item is picked up by the customer, the store must change the order status to the final “Picked Up” status. The retailer’s revenue split becomes payable to them at this point.

These status changes will be indicated in the /carts and /cart API endpoint.

Management via UI; Payment, Communications and Order Handoff

PENDING BRAND:

Once a new order is received, you must go to the order summary page, and update the Ship-to-Store Status.

To confirm or reject the order, click the button to see a drawer pop out with the options.

If you reject the customer’s request, we will notify the customer and the store about the rejection. You can still communicate with the customer about this order through the Buy it Locally messaging window after an order has been rejected.

If you approve the request, the customer’s credit card will be charged immediately for the full amount. Important note: Locally.com will be the company indicated on the credit card statement.

APPROVED:

Once you have started processing the order, you must go to the order summary page, and update the Ship-to-Store Status to “Processing”.

This will end your ability to communicate directly with the customer.

PROCESSING:

Customer is now notified that their order is being processed. The customer and store see a system message indicating that the order is in processing and can message each other about the order.

When the order has been shipped you must go to the order summary page, and update the Ship-to-Store Status to “Shipped”.

The customer and the store will be notified about this in their Buy it Locally messaging window (and via email/SMS).

READY FOR PICKUP:

When the item arrives at the store, it’s the store’s job to alert the customer when the item is ready by changing the order status to “Ready for Pickup.” This is completed via the Order Screen UI all retailers have access to.

PICKED UP:

Once the item is picked up by the customer, the store must change the order status to the final “Picked Up” status. The retailer’s revenue split becomes payable to them at this point.

Billing Considerations

As mentioned earlier, Locally will be charging the customer directly for Ship-to-Store transactions. This is counter to Locally’s usual practice for “Buy Online, Pickup in Store” where the customer’s funds pass directly to the retailer’s merchant account. As is such, Locally will be responsible for paying out the funds to both your company and the retailer. Payouts will be sent on a monthly basis with a physical check to both parties. Please inquire about alternative forms of payment.

At any time you can check the status of the current month’s payouts at https://www.locally.com/station/panel/billing_invoices. Locally audits each statement between the 1st and the 7th of each month and then issues payouts between the 8th and the 12th.

To better understand how accounting works for brands, please refer to the following table which illustrates the purchase of a $1000 item for Ship-to-Store fulfillment:

Handling Returns and Chargebacks

Once an order is confirmed by the brand, the brand can refund the order up until the order is shipped. On orders refunded prior to shipping, the order simply ends there; no revenue is paid out to either the brand or the retailer.

Once the order has shipped, the product becomes the retailer’s. If a shopper requests a refund from the retailer and the retailer issues a refund, the retailer is expected to take the product into their inventory. On these orders, the brand retains their revenue split, and the retailer is charged for the cost of the product by Locally.

In the event that a retailer’s balance with Locally is negative (for example: they have a refunded order whose amount exceeds their owed payouts from other orders), Locally will charge their credit card for the difference.

Frequently Asked Questions

When does an order become payable to the brand and retailer?

The order becomes payable to the brand once the product is marked as ‘shipped,’ and to the retailer once the product is marked as ‘picked up.’

After a customer makes an order, can we change which store we send the product to and notify the customer?

No. The item must be sent to the store that the customer chose initially.

Can the customer change which store they want us to send the item(s) to after they have submitted an order?

Not at this time.

Can we cancel an order and arrange a deal directly with the customer to ship the item(s) to their residence?

No, this is a violation of our terms of service.