Slack slash command that turns a ConnectWise ticket number into a clickable in-channel link.
/cw 1234567 → #1234567 — Replace laptop screen (In Progress | Acme Corp)
When CW API credentials are configured, the response includes the ticket summary, status, and company name. Without API credentials it falls back to a simple link.
Slack slash commands make an HTTP POST to a URL you control. When someone types /cw 1234567, Slack sends a form-encoded request to your server containing the command text. The server validates the input, builds the ConnectWise URL, and returns a JSON response telling Slack what to post.
The full request flow:
User types /cw 1234567
→ Slack POSTs to https://server-url/slack/cw
→ app verifies the request came from Slack
→ app validates the ticket number
→ (optional) app fetches ticket details from CW API
→ app returns JSON with the formatted link
→ Slack posts the message in-channel
The command accepts several ticket number formats:
| Input | Parsed as |
|---|---|
1234567 |
1234567 |
#1234567 |
1234567 |
CW1234567 |
1234567 |
cw-1234567 |
1234567 |
Slack signs every request with your app's signing secret. The app recomputes the signature from the raw request body and timestamp, then compares it against the X-Slack-Signature header using a constant-time comparison to prevent timing attacks. Requests older than 5 minutes are rejected outright to prevent replay attacks.
The app appends the ticket number to CW_BASE_URL:
CW_BASE_URL = https://na.myconnectwise.net/v4_6_release/connectwise.aspx?routeTo=ServiceFV&recid=
ticket = 1234567
result = https://na.myconnectwise.net/v4_6_release/connectwise.aspx?routeTo=ServiceFV&recid=1234567
When the CW API variables are set, the app calls the Manage REST API to fetch the ticket summary, status, and company name. If the lookup fails for any reason the response falls back to a plain link.
cp .env.example .env
# edit .env with your values
docker compose up -dpip install -r requirements.txt
cp .env.example .env
# edit .env with your values
python app.pyFor production, use gunicorn:
gunicorn --bind 0.0.0.0:3000 --workers 2 app:app| Variable | Required | Description |
|---|---|---|
SLACK_SIGNING_SECRET |
Yes | From Slack → Basic Information → App Credentials |
CW_BASE_URL |
Yes | ConnectWise ticket URL prefix (before the ticket number) |
PORT |
No | HTTP port (default 3000) |
CW_API_URL |
No | CW Manage REST API base URL |
CW_COMPANY_ID |
No | Company ID for CW API auth |
CW_PUBLIC_KEY |
No | Public API key |
CW_PRIVATE_KEY |
No | Private API key |
The CW API variables are all optional. If any are missing, the app skips the API lookup and returns a plain link.