You can see a better formatted version of this here: https://wd-kb.com/content/1…6/172/en/one-touch-blf-parking-stopped-working-with-freepbx-_-direct-slot-parking-failure-2026-parking-v17027.html
### FreePBX Version
FreePBX 17
### Issue Description
We found that all of a sudden our one touch parking BLFs to direct parking slots, that's been working for a decade plus stopped working. This affected all 13 of our managed client systems. AI helped out with a remediation below.
FreePBX 17 / Asterisk 20–22: Direct Slot Parking Failure & Pre-Hold Audio Snippet Remediation
Category: FreePBX / VoIP | Applies To: FreePBX 17 (Parking Module v17.0.2.7+), Asterisk 20, 21, 22 | Endpoints: PJSIP (Yealink, Sangoma, Digium)
1. Executive Summary & Symptoms
After updating FreePBX 17 parking modules, PJSIP endpoints attempting to blind transfer calls directly to specific parking slots (e.g., 701, 702) experience two distinct issues:
"No Call Parked" Transfer Failure: Users attempt a blind transfer to a specific slot via BLF/DSS key and hear "We're sorry, there's no call parked on this extension".
Pre-Hold Audio Snippet & Ring Blip: During initial parking or when picking up and re-parking a call, callers hear ~1 second of default Music On Hold (MOH), followed by a brief ring or silence blip, before the parking lot's music stream starts.
2. Root Cause Analysis
Problem 1: macro-parked-call ${BLINDTRANSFER} Variable Check
FreePBX parking module v17.0.2.7 introduced a strict variable check at line 5 of macro-parked-call:
GotoIf($[${LEN(${BLINDTRANSFER})} > 0 | ${LEN(${ATTENDEDTRANSFER})} > 0]?attemptpark)
Native PJSIP REFER blind transfers do not populate the legacy ${BLINDTRANSFER} channel variable. Without this variable set, Asterisk assumes the caller is attempting a pickup rather than a park, executes ParkedCall(), finds no active call in the slot, and plays pbx-invalidpark.
Problem 2: Automatic PJSIP sendonly MOH
When an IP phone performs a transfer or re-park, it sends a SIP a=sendonly (SIP Hold) re-INVITE to Asterisk while constructing the REFER request. By default in PJSIP, suppress_moh_on_sendonly is false. PJSIP immediately starts playing the extension's default Hold Music. A fraction of a second later, when Park() takes over, res_parking interrupts the hold music to start the parking lot stream, causing the double-music blip.
3. Permanent Fleet Fix
Fix Part 1: Dialplan Override (/etc/asterisk/extensions_override_freepbx.conf)
Create or edit /etc/asterisk/extensions_override_freepbx.conf to override [macro-parked-call]. This forces ${BLINDTRANSFER}=PJSIP when a direct slot park attempt is detected and answers/silences the channel immediately at step 1:
[macro-parked-call]
exten => h,1,Gosub(macro-hangupcall,s,1())
exten => s,1,Answer()
exten => s,n,StopMusicOnHold()
exten => s,n,Gosub(macro-user-callerid,s,1())
exten => s,n,Gosub(sub-record-check,s,1(parking,${AMPUSER},${AMPUSER}))
exten => s,n,Noop(PARKRETURNTO: ${SHARED(PARKRETURNTO,${CHANNEL})})
exten => s,n,GotoIf($[${LEN(${SHARED(PARKRETURNTO,${CHANNEL})})} > 0]?backtosender)
exten => s,n,ExecIf($[${LEN(${BLINDTRANSFER})} = 0 & ${LEN(${PARK_GET_CHANNEL(${ARG1},${ARG2})})} = 0]?Set(BLINDTRANSFER=PJSIP))
exten => s,n,GotoIf($[${LEN(${BLINDTRANSFER})} > 0 | ${LEN(${ATTENDEDTRANSFER})} > 0]?attemptpark)
exten => s,n,Set(PARKIE=${PARK_GET_CHANNEL(${ARG1},${ARG2})})
exten => s,n,Set(MIXMON_DIR=${IMPORT(${PARKIE},MIXMON_DIR)})
exten => s,n,Set(YEAR=${IMPORT(${PARKIE},YEAR)})
exten => s,n,Set(MONTH=${IMPORT(${PARKIE},MONTH)})
exten => s,n,Set(DAY=${IMPORT(${PARKIE},DAY)})
exten => s,n,Set(CALLFILENAME=${IMPORT(${PARKIE},CALLFILENAME)})
exten => s,n,Set(MIXMON_FORMAT=${IMPORT(${PARKIE},MIXMON_FORMAT)})
exten => s,n,Set(MIXMON_POST=${IMPORT(${PARKIE},MIXMON_POST)})
exten => s,n,Set(MON_FMT=${IMPORT(${PARKIE},MON_FMT)})
exten => s,n,Set(MIXMON_ID=${IMPORT(${PARKIE},MIXMON_ID)})
exten => s,n,Set(REC_STATUS=${IMPORT(${PARKIE},REC_STATUS)})
exten => s,n,Set(REC_POLICY_MODE=${IMPORT(${PARKIE},REC_POLICY_MODE)})
exten => s,n,Set(RECORD_ID=${IMPORT(${PARKIE},RECORD_ID)})
exten => s,n,GotoIf($[${LEN(${REC_STATUS})} > 0 & "${REC_STATUS}" != "RECORDING"]?next)
exten => s,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => s,n(next),Set(CCSS_SETUP=TRUE)
exten => s,n,GotoIf($[${LEN(${PARKIE})} > 0]?pcall)
exten => s,n,ResetCDR()
exten => s,n,Set(CDR_PROP(disable)=true)
exten => s,n,Wait(1)
exten => s,n,Playback(pbx-invalidpark)
exten => s,n,Wait(1)
exten => s,n,Hangup()
exten => s,n(pcall),Noop(User: ${CALLERID(all)} attempting to pick up Parked Call Slot ${ARG1})
exten => s,n,Noop(PARKIE: ${PARKIE})
exten => s,n,Set(SHARED(PARKRETURNTO,${PARKIE})=)
exten => s,n,Set(PARKOWNER=1)
exten => s,n,ParkedCall(${ARG2},${ARG1})
exten => s,n,Hangup()
exten => s,n(attemptpark),Noop(User: ${CALLERID(all)} attempting to Park into slot ${ARG1})
exten => s,n,Noop(Blind Transfer: ${BLINDTRANSFER}, Attended Transfer: ${ATTENDEDTRANSFER})
exten => s,n,Noop($[${LEN(${PARKOWNER})} = 0])
exten => s,n,GotoIf($[${LEN(${PARKOWNER})} = 0]?parkit)
exten => s,n,Gosub(macro-hangupcall,s,1())
exten => s,n(parkit),Set(PARKINGEXTEN=${ARG1})
exten => s,n,ExecIf($[${LEN(${BLINDTRANSFER})} > 0]?Set(SHARED(PARKRETURNTO,${CHANNEL})=${CUT(BLINDTRANSFER,-,1)}):Set(SHARED(PARKRETURNTO,${CHANNEL})=${CUT(ATTENDEDTRANSFER,-,1)}))
exten => s,n,Noop(PARKRETURNTO: ${SHARED(PARKRETURNTO,${CHANNEL})})
exten => s,n,Park(${ARG2},sc(${CONTEXT},s,200))
exten => s,200(backtosender),Noop(Attempting to go back to sender)
exten => s,n,Set(PARKCALLBACK=${PARKER})
exten => s,n,Set(SHARED(PARKRETURNTO,${CHANNEL})=)
exten => s,n,Goto(park-return-routing,${PARKING_SPACE},1)
Fix Part 2: PJSIP Endpoint Custom Post Config (/etc/asterisk/pjsip.endpoint_custom_post.conf)
To suppress automatic Hold Music during phone a=sendonly transfers, append suppress_moh_on_sendonly=true for each numeric PJSIP extension in /etc/asterisk/pjsip.endpoint_custom_post.conf:
[101](+)
suppress_moh_on_sendonly=true
[102](+)
suppress_moh_on_sendonly=true
[201](+)
suppress_moh_on_sendonly=true
4. Automation Script (Adding/Updating Extensions)
When adding new extensions to a PBX, run this one-liner via SSH to automatically populate /etc/asterisk/pjsip.endpoint_custom_post.conf with all active extensions and reload FreePBX:
exts=$(grep -oE '^[[0-9]+\]' /etc/asterisk/pjsip.endpoint.conf 2>/dev/null | tr -d '[]'); out_file="/etc/asterisk/pjsip.endpoint_custom_post.conf"; > "$out_file"; for ext in $exts; do echo "[$ext](+)"; echo "suppress_moh_on_sendonly=true"; echo ""; done >> "$out_file"; chown asterisk:asterisk "$out_file"; fwconsole reload
5. Verification Commands
1. Verify Dialplan Override Active:
asterisk -rx 'dialplan show macro-parked-call'
2. Verify PJSIP suppress_moh_on_sendonly Active:
asterisk -rx 'pjsip show endpoint 101' | grep -i moh
This feature has been working for a decade plus and now doesn't. We had to use AI to work around the problem as it's widely used. The issue is phone agnostic, we have the same issue with Sangoma and Yealink phones.
FreePBX 17 / Asterisk 20–22: Direct Slot Parking Failure & Pre-Hold Audio Snippet Remediation
Category: FreePBX / VoIP | Applies To: FreePBX 17 (Parking Module v17.0.2.7+), Asterisk 20, 21, 22 | Endpoints: PJSIP (Yealink, Sangoma, Digium)
1. Executive Summary & Symptoms
After updating FreePBX 17 parking modules, PJSIP endpoints attempting to blind transfer calls directly to specific parking slots (e.g., 701, 702) experience two distinct issues:
"No Call Parked" Transfer Failure: Users attempt a blind transfer to a specific slot via BLF/DSS key and hear "We're sorry, there's no call parked on this extension".
Pre-Hold Audio Snippet & Ring Blip: During initial parking or when picking up and re-parking a call, callers hear ~1 second of default Music On Hold (MOH), followed by a brief ring or silence blip, before the parking lot's music stream starts.
**2. Root Cause Analysis
Problem 1: macro-parked-call ${BLINDTRANSFER} Variable Check
FreePBX parking module v17.0.2.7 introduced a strict variable check at line 5 of macro-parked-call:
GotoIf($[${LEN(${BLINDTRANSFER})} > 0 | ${LEN(${ATTENDEDTRANSFER})} > 0]?attemptpark)
Native PJSIP REFER blind transfers do not populate the legacy ${BLINDTRANSFER} channel variable. Without this variable set, Asterisk assumes the caller is attempting a pickup rather than a park, executes ParkedCall(), finds no active call in the slot, and plays pbx-invalidpark.**
Problem 2: Automatic PJSIP sendonly MOH
When an IP phone performs a transfer or re-park, it sends a SIP a=sendonly (SIP Hold) re-INVITE to Asterisk while constructing the REFER request. By default in PJSIP, suppress_moh_on_sendonly is false. PJSIP immediately starts playing the extension's default Hold Music. A fraction of a second later, when Park() takes over, res_parking interrupts the hold music to start the parking lot stream, causing the double-music blip.
3. Permanent Fleet Fix
Fix Part 1: Dialplan Override (/etc/asterisk/extensions_override_freepbx.conf)
Create or edit /etc/asterisk/extensions_override_freepbx.conf to override [macro-parked-call]. This forces ${BLINDTRANSFER}=PJSIP when a direct slot park attempt is detected and answers/silences the channel immediately at step 1:
[macro-parked-call]
exten => h,1,Gosub(macro-hangupcall,s,1())
exten => s,1,Answer()
exten => s,n,StopMusicOnHold()
exten => s,n,Gosub(macro-user-callerid,s,1())
exten => s,n,Gosub(sub-record-check,s,1(parking,${AMPUSER},${AMPUSER}))
exten => s,n,Noop(PARKRETURNTO: ${SHARED(PARKRETURNTO,${CHANNEL})})
exten => s,n,GotoIf($[${LEN(${SHARED(PARKRETURNTO,${CHANNEL})})} > 0]?backtosender)
exten => s,n,ExecIf($[${LEN(${BLINDTRANSFER})} = 0 & ${LEN(${PARK_GET_CHANNEL(${ARG1},${ARG2})})} = 0]?Set(BLINDTRANSFER=PJSIP))
exten => s,n,GotoIf($[${LEN(${BLINDTRANSFER})} > 0 | ${LEN(${ATTENDEDTRANSFER})} > 0]?attemptpark)
exten => s,n,Set(PARKIE=${PARK_GET_CHANNEL(${ARG1},${ARG2})})
exten => s,n,Set(MIXMON_DIR=${IMPORT(${PARKIE},MIXMON_DIR)})
exten => s,n,Set(YEAR=${IMPORT(${PARKIE},YEAR)})
exten => s,n,Set(MONTH=${IMPORT(${PARKIE},MONTH)})
exten => s,n,Set(DAY=${IMPORT(${PARKIE},DAY)})
exten => s,n,Set(CALLFILENAME=${IMPORT(${PARKIE},CALLFILENAME)})
exten => s,n,Set(MIXMON_FORMAT=${IMPORT(${PARKIE},MIXMON_FORMAT)})
exten => s,n,Set(MIXMON_POST=${IMPORT(${PARKIE},MIXMON_POST)})
exten => s,n,Set(MON_FMT=${IMPORT(${PARKIE},MON_FMT)})
exten => s,n,Set(MIXMON_ID=${IMPORT(${PARKIE},MIXMON_ID)})
exten => s,n,Set(REC_STATUS=${IMPORT(${PARKIE},REC_STATUS)})
exten => s,n,Set(REC_POLICY_MODE=${IMPORT(${PARKIE},REC_POLICY_MODE)})
exten => s,n,Set(RECORD_ID=${IMPORT(${PARKIE},RECORD_ID)})
exten => s,n,GotoIf($[${LEN(${REC_STATUS})} > 0 & "${REC_STATUS}" != "RECORDING"]?next)
exten => s,n,Set(CDR(recordingfile)=${CALLFILENAME}.${MON_FMT})
exten => s,n(next),Set(CCSS_SETUP=TRUE)
exten => s,n,GotoIf($[${LEN(${PARKIE})} > 0]?pcall)
exten => s,n,ResetCDR()
exten => s,n,Set(CDR_PROP(disable)=true)
exten => s,n,Wait(1)
exten => s,n,Playback(pbx-invalidpark)
exten => s,n,Wait(1)
exten => s,n,Hangup()
exten => s,n(pcall),Noop(User: ${CALLERID(all)} attempting to pick up Parked Call Slot ${ARG1})
exten => s,n,Noop(PARKIE: ${PARKIE})
exten => s,n,Set(SHARED(PARKRETURNTO,${PARKIE})=)
exten => s,n,Set(PARKOWNER=1)
exten => s,n,ParkedCall(${ARG2},${ARG1})
exten => s,n,Hangup()
exten => s,n(attemptpark),Noop(User: ${CALLERID(all)} attempting to Park into slot ${ARG1})
exten => s,n,Noop(Blind Transfer: ${BLINDTRANSFER}, Attended Transfer: ${ATTENDEDTRANSFER})
exten => s,n,Noop($[${LEN(${PARKOWNER})} = 0])
exten => s,n,GotoIf($[${LEN(${PARKOWNER})} = 0]?parkit)
exten => s,n,Gosub(macro-hangupcall,s,1())
exten => s,n(parkit),Set(PARKINGEXTEN=${ARG1})
exten => s,n,ExecIf($[${LEN(${BLINDTRANSFER})} > 0]?Set(SHARED(PARKRETURNTO,${CHANNEL})=${CUT(BLINDTRANSFER,-,1)}):Set(SHARED(PARKRETURNTO,${CHANNEL})=${CUT(ATTENDEDTRANSFER,-,1)}))
exten => s,n,Noop(PARKRETURNTO: ${SHARED(PARKRETURNTO,${CHANNEL})})
exten => s,n,Park(${ARG2},sc(${CONTEXT},s,200))
exten => s,200(backtosender),Noop(Attempting to go back to sender)
exten => s,n,Set(PARKCALLBACK=${PARKER})
exten => s,n,Set(SHARED(PARKRETURNTO,${CHANNEL})=)
exten => s,n,Goto(park-return-routing,${PARKING_SPACE},1)
Fix Part 2: PJSIP Endpoint Custom Post Config (/etc/asterisk/pjsip.endpoint_custom_post.conf)
To suppress automatic Hold Music during phone a=sendonly transfers, append suppress_moh_on_sendonly=true for each numeric PJSIP extension in /etc/asterisk/pjsip.endpoint_custom_post.conf:
[101](+)
suppress_moh_on_sendonly=true
[102](+)
suppress_moh_on_sendonly=true
[201](+)
suppress_moh_on_sendonly=true
4. Automation Script (Adding/Updating Extensions)
When adding new extensions to a PBX, run this one-liner via SSH to automatically populate /etc/asterisk/pjsip.endpoint_custom_post.conf with all active extensions and reload FreePBX:
exts=$(grep -oE '^[[0-9]+\]' /etc/asterisk/pjsip.endpoint.conf 2>/dev/null | tr -d '[]'); out_file="/etc/asterisk/pjsip.endpoint_custom_post.conf"; > "$out_file"; for ext in $exts; do echo "[$ext](+)"; echo "suppress_moh_on_sendonly=true"; echo ""; done >> "$out_file"; chown asterisk:asterisk "$out_file"; fwconsole reload
5. Verification Commands
1. Verify Dialplan Override Active:
asterisk -rx 'dialplan show macro-parked-call'
2. Verify PJSIP suppress_moh_on_sendonly Active:
asterisk -rx 'pjsip show endpoint 101' | grep -i moh
### Operating Environment
FreePBX 17, fully up to date, Asterisk 22 LTS. We tried other asterisk versions with the same result.
### Relevant log output
```shell
```