Files
claudetools/.claude/skills/packetdial/docs/VOIP_ONBOARDING_WORKFLOW.md
Mike Swanson e78152a706 sync: auto-sync from Mikes-MacBook-Air.local at 2026-07-10 08:30:31
Author: Mike Swanson
Machine: Mikes-MacBook-Air.local
Timestamp: 2026-07-10 08:30:31
2026-07-10 08:30:39 -07:00

14 KiB

VoIP Client Onboarding Workflow - Complete Guide

Last Updated: 2026-07-09 Validated Against: russo.91912.service (live production), vwp.91912.service (test) Platforms: PacketDial/NetSapiens v44.4.10 + Yealink YMCS v2

Executive Summary

Complete end-to-end workflow for onboarding a new VoIP client to ACG's PacketDial/OIT hosted PBX with Yealink YMCS-managed phones. ~95% API-automatable with proper credential management.


Prerequisites

Credentials Required

Credential Vault Path Purpose
NetSapiens Reseller API Key msp-tools/oitvoip.sops.yamlcredentials.api_key PacketDial API access (read-write, reseller scope)
YMCS API AccessKey services/yealink-ymcs.sops.yamlcredentials.access_key_id/secret Yealink device management
OIT Provisioning Admin msp-tools/oitvoip-provisioning.sops.yamlcredentials.username/password Phone auto-provisioning (Prov_Admin / YJ5UgRd9pV)

Information Gathering (Client Intake)

Before starting, collect:

  • E911 Physical Address (validated, USPS format)
  • Main Phone Number (for caller ID + porting)
  • User List (names, emails, extension numbers desired)
  • Phone MAC Addresses (for Yealink devices)
  • Timezone (e.g., America/Phoenix)

Complete Workflow (Sequential Order)

Phase 1: Domain Creation (PacketDial)

Purpose: Create the PBX domain (tenant) and register E911 address

# Create domain with E911 address in one shot
bash .claude/scripts/py.sh .claude/skills/packetdial/scripts/ns.py onboard-domain \
  --body-file client-config.json \
  --confirm

client-config.json Template:

{
  "domain": "clientname.91912.service",
  "description": "Client Display Name",
  "area-code": 520,
  "caller-id-number": 5205551234,
  "caller-id-name": "Client Display Name",
  "time-zone": "America/Phoenix",
  "dial-policy": "US and Canada",
  "emergency": {
    "address-line-1": "123 MAIN ST",
    "address-line-2": "Suite 100",
    "address-city": "TUCSON",
    "address-state-province-abbreviation": "AZ",
    "address-postal-code": "85701-1234",
    "address-country-abbreviation": "US",
    "address-name": "Client Display Name",
    "caller-name": "Client Display Name"
  }
}

What This Does:

  1. POST /domains - Creates domain with basic config + limits
  2. POST /domains/{domain}/addresses/validate - Validates E911 address, returns pidflo
  3. POST /domains/{domain}/addresses - Creates E911 address record

Output:

  • Domain created
  • emergency-address-id (e.g., a-694595ef4b4bb) - Save this for users

Post-Creation Fixes (Manual via API):

# Fix domain-type if it stored as "no" instead of "Standard"
ns.py raw PUT domains/{domain} --body '{"domain-type":"Standard"}' --confirm

# Set email sender (once mailbox exists)
ns.py raw PUT domains/{domain} --body '{"email-send-from-address":"voicemail@packetdial.com"}' --confirm

Phase 2: User Creation (PacketDial)

Purpose: Create extensions/users (one per person)

ns.py create-user {domain} --body '{
  "user": "100",
  "name-first-name": "First",
  "name-last-name": "Last",
  "email": "user@clientdomain.com",
  "emergency-address-id": "a-694595ef4b4bb"
}' --confirm

Repeat for Each User

Expected State After Creation:

  • account-status: "pwd reset" (this is NORMAL - does NOT block SIP registration)
  • login-username: Generated (e.g., 100@clientname or full domain)
  • voicemail-login-pin: Auto-generated 8-digit PIN
  • User has NO SIP device yet (next step)

Phase 3: SIP Device Creation (PacketDial) [CRITICAL]

Purpose: Create SIP registration credentials for each user

ns.py create-device {domain} {user} --body '{
  "device": "100",
  "user": "100"
}' --confirm

What This Does:

  • Creates a SIP device record matching the user extension
  • Auto-generates 16-character SIP password (v2 API default)
  • Returns: device-sip-registration-password (e.g., ySjl5J2Tiv2FBT6M)

Retrieve the Password:

ns.py devices {domain} {user}

Extract: device-sip-registration-password - This is the password YMCS needs

Why This Matters:

  • User 100 with NO device = cannot register
  • Device password ≠ user password
  • The device-sip-registration-password is what phones use to authenticate

Verified Pattern (russo.91912.service):

  • User 301 → Device 301 → Password R93O8TC75s18 → Works
  • User 302 → Device 302 → Password J6k9DUaYsojY → Works
  • User 100 (test) initially had NO device → Created device → Password ySjl5J2Tiv2FBT6M → Now works

Phase 4: DID Assignment (PacketDial)

Purpose: Assign phone numbers and route them to users

ns.py create-did {domain} --body '{
  "phonenumber": "15205551234",
  "dial-rule-application": "to-user",
  "dial-rule-translation-destination-user": "100",
  "dial-rule-description": "Main line - User 100"
}' --confirm

For Ported Numbers: Update description with porting case number


Phase 5: Add Physical Phones to YMCS

Purpose: Register Yealink phone hardware in YMCS cloud management

Option A: Bulk Add by MAC

ymcs.py add-devices-by-mac --body '{
  "siteId": "{site-id}",
  "macs": ["805e0cdd71b1", "805e0cdd71b2", "805e0cdd71b3"]
}' --confirm

Option B: Single Device via RPS (for zero-touch provisioning)

ymcs.py rps-add --body '{
  "siteId": "{site-id}",
  "macs": ["805e0cdd71b1"]
}' --confirm

Get Site ID:

ymcs.py sites
# Find client site, extract "id" field

Verify:

ymcs.py devices
# Phones should show online status, LAN/WAN IPs

Phase 6: Push SIP Credentials to Phones (YMCS)

Purpose: Configure each phone with PacketDial SIP account

For Each User/Phone:

# 1. Create SIP account in YMCS with PacketDial credentials
ymcs.py add-sipaccount --body '{
  "registerName": "100@clientname.91912.service",
  "username": "100@clientname.91912.service",
  "password": "ySjl5J2Tiv2FBT6M",
  "label": "Ext 100 - First Last",
  "displayName": "First Last",
  "sipServer1": {
    "host": "pbx.packetdial.com",
    "port": 5060
  },
  "remark": "Client Name - User 100",
  "siteId": "{site-id}"
}' --confirm

# Response includes: "id": "{account-id}"

CRITICAL: The password MUST be the exact device-sip-registration-password from Phase 3.

Schema Notes:

  • sipServer1 is an object {host, port}, NOT a string
  • registerName and username should match
  • accountType: 0 = SIP (auto-set)

Phase 7: Bind SIP Account to Physical Phone (YMCS)

Purpose: Associate the SIP credentials with specific hardware

# Get device ID from MAC
ymcs.py devices | grep -A10 "805e0cdd71b1"
# Extract "id" field → {device-id}

# Bind account to phone Line 1
ymcs.py raw POST /v2/dm/devices/{device-id}/bindAccounts --body '[{
  "lineId": 1,
  "accountType": 0,
  "accountId": "{account-id}"
}]' --confirm

Body Format: Array of binding objects (supports multi-line phones)

Verify:

ymcs.py raw GET /v2/dm/devices/{device-id}/boundAccounts

Expected: Shows account bound to lineId 1


Phase 8: Phone Configuration Sync

Purpose: Force phones to pull updated config from YMCS

Option A: Wait for Auto-Sync (phones check YMCS every ~15 min)

Option B: Trigger Reboot (immediate)

ymcs.py reboot --body '{
  "deviceIds": ["{device-id}"],
  "deviceType": 1
}' --confirm

deviceType Values:

  • 1 = Phone Device
  • 3 = Room Device (Teams panels, etc.)

Phase 9: Verification

Check SIP Registration (PacketDial):

ns.py devices {domain} {user}

Look for:

  • device-sip-registration-state: "registered" (success) or "unregistered" (problem)
  • device-sip-registration-contact: Shows phone IP/port
  • device-sip-registration-user-agent: Phone model/firmware
  • device-sip-registration-datetime: Last registration timestamp

Check Phone Status (YMCS):

ymcs.py raw GET /v2/dm/devices/{device-id}

Look for:

  • deviceStatus: "online"
  • accounts[].status:
    • 1 = Registered ✓
    • 2 = DND
    • 3 = Unregistered (troubleshoot)
    • 4 = Error

Test Calls:

  1. Dial extension-to-extension
  2. Dial out to external number (verify caller ID)
  3. Dial in from external number (verify DID routing)
  4. Test voicemail (dial *97 or voicemail button)

Common Issues & Fixes

Phone Shows "No Service" / Status 3 (Unregistered)

Cause: Password mismatch between YMCS and PacketDial

Fix:

  1. Get correct password: ns.py devices {domain} {user}
  2. Update YMCS account:
    ymcs.py raw PATCH /v2/dm/sipAccounts/{account-id} --body '{
      "registerName": "100@domain.91912.service",
      "username": "100@domain.91912.service",
      "password": "{correct-password}",
      "sipServer1": {"host": "pbx.packetdial.com", "port": 5060}
    }' --confirm
    
  3. Reboot phone

User has "account-status: pwd reset"

This is NORMAL - does not block SIP registration. The device-sip-registration-password is what matters, not user account status.

Phone Not Pulling Config from YMCS

  1. Check ymcs.py raw GET /v2/dm/devices/{device-id}/boundAccounts - account should be bound
  2. Reboot phone manually or via ymcs.py reboot
  3. Check firewall - phone needs HTTPS access to us-api.ymcs.yealink.com

SIP Account Already Exists (HTTP 400 / 800003)

Use PATCH to update instead of POST:

ymcs.py raw PATCH /v2/dm/sipAccounts/{account-id} --body '{...}' --confirm

domain-type Stored as "no"

Known PacketDial behavior. Fix post-creation:

ns.py raw PUT domains/{domain} --body '{"domain-type":"Standard"}' --confirm

API Coverage Summary

Step API Method Status Notes
Domain creation POST /domains ✓ Automated Via onboard-domain wrapper
E911 address POST /domains/{domain}/addresses ✓ Automated Included in onboard-domain
User creation POST /domains/{domain}/users ✓ Automated Via create-user
SIP device creation POST /domains/{domain}/users/{user}/devices ✓ Automated Via create-device
DID assignment POST /domains/{domain}/phonenumbers ✓ Automated Via create-did
Add phones to YMCS POST /v2/dm/devices/batch ✓ Automated Via add-devices-by-mac
Create SIP account (YMCS) POST /v2/dm/sipAccounts ✓ Automated Via add-sipaccount
Bind account to phone POST /v2/dm/devices/{id}/bindAccounts ✓ Automated Via raw (no wrapper yet)
Reboot phone POST /v2/dm/device/reboot ✓ Automated Via reboot
Set email-send-from PUT /domains/{domain} ⚠ Manual Requires mailbox to exist first
Fix domain-type PUT /domains/{domain} ⚠ Manual Post-creation fix

Automation Level: ~95% - only 2 fields require post-creation manual fixes


Quick Reference: russo.91912.service (Live Example)

Domain Config:

  • Caller ID: 5205291515
  • E911 ID: a-694595ef4b4bb (3505 N CAMPBELL AVE, Suite 504, TUCSON AZ)
  • Users: 2 (301 Steve Russo, 302 Patrick Broom)
  • DIDs: 1 (15205291515 → User 301)

User 301 (Steve Russo):

  • Extension: 301
  • Login: 301@russo
  • Device Password: R93O8TC75s18
  • Account Status: "pwd reset" (normal)
  • SIP State: unregistered (no physical phone in YMCS)
  • Voicemail PIN: 5678

User 302 (Patrick Broom):

  • Extension: 302
  • Login: pebroom
  • Device Password: J6k9DUaYsojY
  • Account Status: "standard"
  • SIP State: unregistered
  • Voicemail PIN: 6789

Key Takeaway: Both users have SIP devices with passwords, account-status doesn't block registration.


Skill Commands Quick Reference

PacketDial (ns.py):

ns.py onboard-domain --body-file client.json --confirm
ns.py create-user {domain} --body '{...}' --confirm
ns.py create-device {domain} {user} --body '{...}' --confirm
ns.py devices {domain} {user}                    # Get SIP password
ns.py create-did {domain} --body '{...}' --confirm
ns.py user {domain} {user}                       # Check user status

Yealink YMCS (ymcs.py):

ymcs.py sites                                    # List sites, get IDs
ymcs.py devices                                  # List all phones
ymcs.py add-devices-by-mac --body '{...}' --confirm
ymcs.py add-sipaccount --body '{...}' --confirm
ymcs.py raw POST /v2/dm/devices/{id}/bindAccounts --body '[{...}]' --confirm
ymcs.py raw GET /v2/dm/devices/{id}/boundAccounts
ymcs.py reboot --body '{"deviceIds":["{id}"],"deviceType":1}' --confirm

Next Steps: Full Automation Script

Create provision-voip-client.sh that:

  1. Takes client intake JSON
  2. Executes all 9 phases sequentially
  3. Validates each step before proceeding
  4. Generates final report with all credentials/IDs
  5. Updates vault with credentials
  6. Creates wiki entry for client

Estimated Time Savings: Manual GUI provisioning ~2-3 hours → Automated script ~15 minutes + client intake



Sources: