Closed Loop System
Confirming pick-to-light picks with callback acknowledgements and transaction tracking.
What Is a Closed Loop System?
Running a closed-loop system lets your server know whether the picker acknowledged the pick-to-light task. This feedback loop is essential for maintaining accurate inventory and ensuring every pick is verified.
Modern devices support barcodes and QR-Codes that are a natural choice for transaction confirmations. In fact, the encoded data can be customized for each individual pick.
Callback Confirmations
To track each pick, include a unique transaction ID with the command. In the REST API, this field is called a nonce. When the picker responds, Voodoo sends that same ID back to your server so you know exactly which pick was confirmed.
REST API Example
import requests, json
session = requests.Session()
url = "https://www.voodoodevices.com/api/"
# Login
x = session.post(url + "user/login/",
json={'username': 'yourusername', 'password': 'yourpassword'})
z = json.loads(x.text)
# Send command with nonce for closed-loop tracking
session.post(
url + "device/D4F660:AFA0CB/",
headers={'referer': url, 'x-csrf-token': z['token']},
json={
'command': 'flash',
'line1': 'Jessica',
'line2': 'Pick 3',
'line3': 'SKU:001256',
'nonce': 'transactionID001',
'seconds': 120
}
)QueryString Example
https://www.voodoodevices.com/api/D4F660:AFA0CB/pick/Jessica Pick 3/SKU:001256/140,c5,4/20/transactionID001Acknowledgement State Model
After you send a command with a transaction ID, your server can receive one of three results:
The picker confirmed the task by pressing the button. Your callback URL receives:
https://your.domain.com/voodoodevices/transactionID001/ackThe timer expired and the picker did not confirm the task. Your callback URL receives:
https://your.domain.com/voodoodevices/transactionID001/nackThe device did not respond, usually because it was out of range or disconnected. By default, no callback is sent.
Callback URL Configuration
The prefix of the response URL (e.g., http://your.domain.com:8000/voodoodevices/) is a string you choose and enter in www.voodoodevices.com on the Settings page. The URL can be on any port, use HTTP or HTTPS and use GET or POST methods.
Inbound Connectivity Required
www.voodoodevices.com, the callback is sent from Voodoo's cloud server on the public Internet to the URL you provide. If that destination is a server inside your corporate firewall, most firewalls will block the connection by default because it is an inbound request from the Internet into your private network. For that reason, you must allow inbound messages from www.voodoodevices.com to reach that internal server. If your callback URL points to a public cloud endpoint instead, no firewall change is needed. If your environment cannot permit that inbound access, you may be a better fit for a self-hosted server deployment. See the Server Deployment guide.Receiving the Callback
Your server needs an endpoint that Voodoo can reach. Below is a minimal Python example that listens on port 8000 and prints every callback it receives — a useful starting point before you wire the result into your own order or inventory system.
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
# Voodoo calls back using either GET or POST, depending on
# the method you configured on the Settings page.
#
# GET example — result encoded in the URL path:
# GET /voodoodevices/transactionID001/ack
#
# POST example — result sent as a JSON body:
# {
# "acknack": "ACK",
# "location": "A1-SHELF002",
# "deviceid": "D4F660",
# "nonce": "transactionID001",
# "qty": 3
# }
class CallbackHandler(BaseHTTPRequestHandler):
def do_GET(self):
print(f"GET {self.path}")
self.send_response(200)
self.end_headers()
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
data = json.loads(body)
print(f"POST {self.path}")
print(json.dumps(data, indent=2))
self.send_response(200)
self.end_headers()
if __name__ == "__main__":
server = HTTPServer(("0.0.0.0", 8000), CallbackHandler)
print("Listening on port 8000 ...")
server.serve_forever()ACK/NACK and Command Lifecycle
nonce). Once a command is acknowledged or times out, it clears and the device falls back to its persistent statics. Understanding this distinction is crucial for designing reliable pick-to-light workflows. See the Commands vs. Statics Quick Reference for complete details on timeout behavior, seconds vs. displaytimeout, and best practices.Common Patterns
Here are common integration patterns for closed-loop systems:
- Simple confirmation: Use the transaction ID to mark a pick as complete in your WMS when an "ack" is received.
- Timeout handling: Re-send the command or escalate to a supervisor when a "nack" is received.
- Barcode verification: Display a unique barcode on the device for each pick. The picker scans the barcode to confirm, and a kill command is sent to the device. No button push needed!
- Quantity adjustment: Use multi-button devices to let pickers adjust quantity before acknowledgement. The final quantity is sent along with the ack/nack callback.
Summary Flow
Related Pages
Commands vs. Statics
Understand command lifecycle, ACK/NACK, and timeout behavior
REST API Overview
Full API reference including nonce parameter
Best Practices
Deployment patterns and operational tips
Line Encodings
Barcodes, QR codes, and icons for verification
Sequence API
Automated sequence management with acknowledgements
