function requestBody(config,contact){ if(!config||!['phone','messaging','complete'].includes(config.profile)||typeof config.allowPaid!=='boolean'||!Number.isSafeInteger(config.maximumMicros)||config.maximumMicros<0||config.maximumMicros>1000000||!['GBP','USD','EUR'].includes(config.currency))throw Error('Review the profile, currency, spending cap, and paid permission.'); if(!contact||contact.locationId!==config.locationId||!['locationId','workflowId'].every(k=>/^[A-Za-z0-9_-]{1,40}$/.test(config[k]||''))||!/^[A-Za-z0-9_-]{1,40}$/.test(contact.id||'')||!/^[A-Za-z0-9._:+-]{1,64}$/.test(contact.revision||''))throw Error('A matching location, permanent contact ID, and explicit source-phone revision are required.'); if(typeof contact.phone!=='string'||!contact.phone.trim()||contact.phone.length>250||contact.country&&!/^[A-Za-z]{2}$/.test(contact.country))throw Error('A phone number and a known ISO country for national input are required.'); const body={locationId:config.locationId,workflowId:config.workflowId,recordId:contact.id,revision:contact.revision,profile:config.profile,phone:contact.phone,...(contact.country?{default_country:contact.country}:{}),currency:config.currency,maximumMicros:config.maximumMicros,allowPaid:config.allowPaid}; // A serialized JSON body prevents a contact value from injecting new JSON keys. return {body:JSON.stringify(body),receipt:JSON.stringify({locationId:body.locationId,workflowId:body.workflowId,recordId:body.recordId,revision:body.revision,phone:body.phone,country:body.default_country?.toUpperCase()??null,profile:body.profile}),paidEnabled:body.allowPaid&&body.maximumMicros>0}; } function mappedContact(input){ if(!input||typeof input!=='object'||Array.isArray(input))throw Error('Native contact inputs required.'); // HighLevel parses number-like input values, including a leading +. // A fixed text prefix prevents coercion; never reconstruct lost digits. if(typeof input.sourcePhone!=='string'||!input.sourcePhone.startsWith('phone:'))throw Error('Map the source phone with its phone: text prefix.'); const fields={locationId:input.locationId,id:input.contactId,phone:input.sourcePhone.slice(6),revision:input.sourceRevision}; if(Object.values(fields).some(v=>typeof v!=='string'||!v||/\{\{.*\}\}/.test(v)))throw Error('Resolve the native contact fields before running.'); // Empty native fields may arrive as numeric 0. Preserve emptiness with a // literal prefix at the mapping boundary; never infer an ID from a number. const country=typeof input.sourceCountry==='string'&&input.sourceCountry.startsWith('country:')?input.sourceCountry.slice(8):input.sourceCountry; if(country!==undefined&&country!==''&&(typeof country!=='string'||!/^[A-Za-z]{2}$/.test(country)))throw Error('Use a known two-letter country or leave it empty for international input.'); const lastRequestId=typeof input.lastRequestId==='string'&&input.lastRequestId.startsWith('request:')?input.lastRequestId.slice(8):input.lastRequestId; if(lastRequestId!==undefined&&(typeof lastRequestId!=='string'||lastRequestId.length>250||/\{\{.*\}\}/.test(lastRequestId)))throw Error('Request reference must be resolved text.'); return{...fields,...(country?{country:country.toUpperCase()}:{}),lastRequestId:lastRequestId||''}; } function nativeObject(value,name){ let parsed=value; if(typeof value==='string'){ try{parsed=JSON.parse(value);}catch(error){ // Native workflow interpolation HTML-encodes the JSON string. Decode // exactly one layer only when the opening JSON key is visibly encoded. if(!/^\s*\{(?:"|"|")/i.test(value))throw error; const entities={'quot':'"','amp':'&','apos':"'",'lt':'<','gt':'>'}; const decoded=value.replace(/&(#x[0-9a-f]+|#[0-9]+|quot|amp|apos|lt|gt);/gi,(entity,key)=>{ if(key[0]!=='#')return entities[key.toLowerCase()]??entity; const point=key[1].toLowerCase()==='x'?parseInt(key.slice(2),16):parseInt(key.slice(1),10); return point>0&&point<=0x10ffff?String.fromCodePoint(point):entity; }); parsed=JSON.parse(decoded); } } if(!parsed||typeof parsed!=='object'||Array.isArray(parsed))throw Error(name+' must be a JSON object.'); return parsed; } var output = requestBody(nativeObject(inputData.config,'config'), mappedContact(inputData)); return output;