[{"data":1,"prerenderedAt":2503},["ShallowReactive",2],{"$tfMlIxKXpI":3},[4,1020,1228,2276,2441],{"id":5,"title":6,"body":7,"date":1009,"description":1010,"excerpt":1011,"extension":1012,"meta":1013,"navigation":636,"path":1016,"seo":1017,"stem":1018,"__hash__":1019},"blog\u002Fblog\u002Fhigh-level-sso-custom-links.md","HighLevel: Utilizing SSO sessions from inside Custom Menu Links",{"type":8,"value":9,"toc":998},"minimark",[10,22,25,34,39,42,45,56,60,63,68,102,155,165,200,204,207,310,317,347,350,369,373,380,432,435,460,464,471,498,520,572,576,591,595,598,994],[11,12,13,14,21],"p",{},"I'm working on an extensive custom integration with ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fwww.gohighlevel.com\u002F",[19],"nofollow","HighLevel",", now possible because they recently built a single-sign-on process that allows web applications embedded in iFrames to validate that they are being loaded within the HL interface, as well as retrieve information about the current user and account viewing the application.",[11,23,24],{},"Unfortunately, the SSO functionality is currently only available on a secondary tab of the App. In addition, there can only be one Custom Page. This seems useful for a settings page, but there is so much more potential here.",[11,26,27,28,33],{},"Using some custom JavaScript, you can recreate the SSO functionality from any Custom Menu Link. If you haven't already learned how to implement this from inside the iFrame, I suggest you head over to ",[15,29,32],{"href":30,"rel":31},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=3rveQDuVlR0",[19],"Sergio Leon's video explaining the SSO process",".",[35,36,38],"h2",{"id":37},"setup","Setup",[11,40,41],{},"To start this, you will need an Application set up and installed, and an SSO Key generated for it.",[11,43,44],{},"You will need to know the Application ID and Location ID. You can insert the location ID into the link dynamically. From the tooltip on the Menu Link edit screen:",[46,47,48],"blockquote",{},[11,49,50,51],{},"You may use Location values and Custom Values in here. (These will only work on the Location side bar, when inside an account) Example: ",[15,52,55],{"href":53,"rel":54},"http:\u002F\u002Furl.com\u002F%7B%7Blocation.id%7D%7D?value=%7B%7Bcustom_values.my_value%7D%7D",[19],"http:\u002F\u002Furl.com\u002F{{location.id}}?value={{custom_values.my_value}}",[35,57,59],{"id":58},"custom-link-sso-implementation","Custom Link SSO Implementation",[11,61,62],{},"In your Settings > Company page, there is a Custom Javascript box. This code will go in that box.",[64,65,67],"h3",{"id":66},"event-listener","Event Listener",[11,69,70,71,76,77,81,82,85,86,91,92,97,98,101],{},"We need to re-create the event listener that GHL provides on the Application Custom Page tab. This is a standard ",[15,72,75],{"href":73,"rel":74},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FEventTarget\u002FaddEventListener",[19],"event listener"," attatched to the window. This listens for a ",[78,79,80],"code",{},"message"," event and executes the code. It is important that we set up an ",[78,83,84],{},"async"," function, since this will handle ",[15,87,90],{"href":88,"rel":89},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FGlobal_Objects\u002FPromise",[19],"Promises",". We're using ",[15,93,96],{"href":94,"rel":95},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FJavaScript\u002FReference\u002FOperators\u002FDestructuring_assignment#:~:text=Unpacking%20properties%20from%20objects%20passed%20as%20a%20function%20parameter",[19],"destructuring"," to pull just the ",[78,99,100],{},"data"," object out of the event.",[103,104,109],"pre",{"className":105,"code":106,"language":107,"meta":108,"style":108},"language-js shiki shiki-themes github-light github-dark","window.addEventListener(\"message\", async function({ data }){ ... });\n","js","",[78,110,111],{"__ignoreMap":108},[112,113,116,120,124,127,131,134,137,140,143,146,149,152],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sVt8B","window.",[112,121,123],{"class":122},"sScJk","addEventListener",[112,125,126],{"class":118},"(",[112,128,130],{"class":129},"sZZnC","\"message\"",[112,132,133],{"class":118},", ",[112,135,84],{"class":136},"szBVR",[112,138,139],{"class":136}," function",[112,141,142],{"class":118},"({ ",[112,144,100],{"class":145},"s4XuR",[112,147,148],{"class":118}," }){ ",[112,150,151],{"class":136},"...",[112,153,154],{"class":118}," });\n",[11,156,157,158,160,161,164],{},"Since the ",[78,159,80],{}," event is used by many functions, you want to make sure this is the event we're looking for. The name used by the official GHL implementation is ",[78,162,163],{},"REQUEST_USER_DATA",". If you change this, it needs to match on both the custom listener and the app loaded inside the iFrame.",[103,166,168],{"className":105,"code":167,"language":107,"meta":108,"style":108},"if (data.message === \"REQUEST_USER_DATA\") {\n  \u002F\u002F...\n}\n",[78,169,170,187,194],{"__ignoreMap":108},[112,171,172,175,178,181,184],{"class":114,"line":115},[112,173,174],{"class":136},"if",[112,176,177],{"class":118}," (data.message ",[112,179,180],{"class":136},"===",[112,182,183],{"class":129}," \"REQUEST_USER_DATA\"",[112,185,186],{"class":118},") {\n",[112,188,190],{"class":114,"line":189},2,[112,191,193],{"class":192},"sJ8bj","  \u002F\u002F...\n",[112,195,197],{"class":114,"line":196},3,[112,198,199],{"class":118},"}\n",[64,201,203],{"id":202},"authenticated-api-requests","Authenticated API requests",[11,205,206],{},"We'll need to be able to make an authenticated request for the session details. All API requests from the browser require these headers:",[103,208,210],{"className":105,"code":209,"language":107,"meta":108,"style":108},"const requestHeaders = new Headers();\nrequestHeaders.append(\"source\", \"WEB_USER\");\nrequestHeaders.append(\"version\", \"2021-07-28\");\nrequestHeaders.append(\"accept\", \"application\u002Fjson, text\u002Fplain, *\u002F*\");\nrequestHeaders.append(\"channel\", \"APP\");\n",[78,211,212,233,254,272,291],{"__ignoreMap":108},[112,213,214,217,221,224,227,230],{"class":114,"line":115},[112,215,216],{"class":136},"const",[112,218,220],{"class":219},"sj4cs"," requestHeaders",[112,222,223],{"class":136}," =",[112,225,226],{"class":136}," new",[112,228,229],{"class":122}," Headers",[112,231,232],{"class":118},"();\n",[112,234,235,238,241,243,246,248,251],{"class":114,"line":189},[112,236,237],{"class":118},"requestHeaders.",[112,239,240],{"class":122},"append",[112,242,126],{"class":118},[112,244,245],{"class":129},"\"source\"",[112,247,133],{"class":118},[112,249,250],{"class":129},"\"WEB_USER\"",[112,252,253],{"class":118},");\n",[112,255,256,258,260,262,265,267,270],{"class":114,"line":196},[112,257,237],{"class":118},[112,259,240],{"class":122},[112,261,126],{"class":118},[112,263,264],{"class":129},"\"version\"",[112,266,133],{"class":118},[112,268,269],{"class":129},"\"2021-07-28\"",[112,271,253],{"class":118},[112,273,275,277,279,281,284,286,289],{"class":114,"line":274},4,[112,276,237],{"class":118},[112,278,240],{"class":122},[112,280,126],{"class":118},[112,282,283],{"class":129},"\"accept\"",[112,285,133],{"class":118},[112,287,288],{"class":129},"\"application\u002Fjson, text\u002Fplain, *\u002F*\"",[112,290,253],{"class":118},[112,292,294,296,298,300,303,305,308],{"class":114,"line":293},5,[112,295,237],{"class":118},[112,297,240],{"class":122},[112,299,126],{"class":118},[112,301,302],{"class":129},"\"channel\"",[112,304,133],{"class":118},[112,306,307],{"class":129},"\"APP\"",[112,309,253],{"class":118},[11,311,312,313,316],{},"Then this header is what provides the current user's token. ",[78,314,315],{},"window.getToken()"," is provided by the primary GHL application.",[103,318,320],{"className":105,"code":319,"language":107,"meta":108,"style":108},"requestHeaders.append(\"token-id\", await window.getToken());\n",[78,321,322],{"__ignoreMap":108},[112,323,324,326,328,330,333,335,338,341,344],{"class":114,"line":115},[112,325,237],{"class":118},[112,327,240],{"class":122},[112,329,126],{"class":118},[112,331,332],{"class":129},"\"token-id\"",[112,334,133],{"class":118},[112,336,337],{"class":136},"await",[112,339,340],{"class":118}," window.",[112,342,343],{"class":122},"getToken",[112,345,346],{"class":118},"());\n",[11,348,349],{},"For this SSO request, you'll also need to provide the location ID in a header.",[103,351,353],{"className":105,"code":352,"language":107,"meta":108,"style":108},"requestHeaders.append(\"location\", locationId);\n",[78,354,355],{"__ignoreMap":108},[112,356,357,359,361,363,366],{"class":114,"line":115},[112,358,237],{"class":118},[112,360,240],{"class":122},[112,362,126],{"class":118},[112,364,365],{"class":129},"\"location\"",[112,367,368],{"class":118},", locationId);\n",[64,370,372],{"id":371},"retrieving-session-details","Retrieving Session Details",[11,374,375,376,379],{},"We can get the details by making a request to the ",[78,377,378],{},"session-details"," endpoint with those headers and the App ID.",[103,381,383],{"className":105,"code":382,"language":107,"meta":108,"style":108},"const sessionDetailsResponse = fetch(`https:\u002F\u002Fservices.leadconnectorhq.com\u002Foauth\u002Fsso\u002F${appId}\u002Fsession-details`, {\n  method: \"GET\",\n  headers: requestHeaders,\n});\n",[78,384,385,411,422,427],{"__ignoreMap":108},[112,386,387,389,392,394,397,399,402,405,408],{"class":114,"line":115},[112,388,216],{"class":136},[112,390,391],{"class":219}," sessionDetailsResponse",[112,393,223],{"class":136},[112,395,396],{"class":122}," fetch",[112,398,126],{"class":118},[112,400,401],{"class":129},"`https:\u002F\u002Fservices.leadconnectorhq.com\u002Foauth\u002Fsso\u002F${",[112,403,404],{"class":118},"appId",[112,406,407],{"class":129},"}\u002Fsession-details`",[112,409,410],{"class":118},", {\n",[112,412,413,416,419],{"class":114,"line":189},[112,414,415],{"class":118},"  method: ",[112,417,418],{"class":129},"\"GET\"",[112,420,421],{"class":118},",\n",[112,423,424],{"class":114,"line":196},[112,425,426],{"class":118},"  headers: requestHeaders,\n",[112,428,429],{"class":114,"line":274},[112,430,431],{"class":118},"});\n",[11,433,434],{},"Get the JSON payload from request",[103,436,438],{"className":105,"code":437,"language":107,"meta":108,"style":108},"const sessionDetails = await sessionDetailsResponse.json();\n",[78,439,440],{"__ignoreMap":108},[112,441,442,444,447,449,452,455,458],{"class":114,"line":115},[112,443,216],{"class":136},[112,445,446],{"class":219}," sessionDetails",[112,448,223],{"class":136},[112,450,451],{"class":136}," await",[112,453,454],{"class":118}," sessionDetailsResponse.",[112,456,457],{"class":122},"json",[112,459,232],{"class":118},[64,461,463],{"id":462},"response-event","Response Event",[11,465,466,467,470],{},"Now that we have the encrypted session data, when we want to reply to the iFrame. We'll need to have a target DOM element to send the message to. In this case, its the iFrame inside the ",[78,468,469],{},".custom-link"," container.",[103,472,474],{"className":105,"code":473,"language":107,"meta":108,"style":108},"const frame = document.querySelector(\".custom-link iframe\");\n",[78,475,476],{"__ignoreMap":108},[112,477,478,480,483,485,488,491,493,496],{"class":114,"line":115},[112,479,216],{"class":136},[112,481,482],{"class":219}," frame",[112,484,223],{"class":136},[112,486,487],{"class":118}," document.",[112,489,490],{"class":122},"querySelector",[112,492,126],{"class":118},[112,494,495],{"class":129},"\".custom-link iframe\"",[112,497,253],{"class":118},[11,499,500,501,506,507,510,511,515,516,519],{},"We're looking for the ",[15,502,505],{"href":503,"rel":504},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FHTMLIFrameElement\u002FcontentWindow",[19],"contentWindow"," property of the iFrame. This gives us the ",[78,508,509],{},"window"," object inside the iFrame. Now we can post a message ",[512,513,514],"em",{},"inside"," the iFrame with the session details. The official implementation uses the message name ",[78,517,518],{},"REQUEST_USER_DATA_RESPONSE",", again if you change that here, you must change it on the app loaded inside the iFrame.",[103,521,523],{"className":105,"code":522,"language":107,"meta":108,"style":108},"frame.contentWindow.postMessage(\n  {\n    message: \"REQUEST_USER_DATA_RESPONSE\",\n    payload: sessionDetails.data,\n  },\n  \"*\"\n);\n",[78,524,525,536,541,551,556,561,567],{"__ignoreMap":108},[112,526,527,530,533],{"class":114,"line":115},[112,528,529],{"class":118},"frame.contentWindow.",[112,531,532],{"class":122},"postMessage",[112,534,535],{"class":118},"(\n",[112,537,538],{"class":114,"line":189},[112,539,540],{"class":118},"  {\n",[112,542,543,546,549],{"class":114,"line":196},[112,544,545],{"class":118},"    message: ",[112,547,548],{"class":129},"\"REQUEST_USER_DATA_RESPONSE\"",[112,550,421],{"class":118},[112,552,553],{"class":114,"line":274},[112,554,555],{"class":118},"    payload: sessionDetails.data,\n",[112,557,558],{"class":114,"line":293},[112,559,560],{"class":118},"  },\n",[112,562,564],{"class":114,"line":563},6,[112,565,566],{"class":129},"  \"*\"\n",[112,568,570],{"class":114,"line":569},7,[112,571,253],{"class":118},[35,573,575],{"id":574},"custom-menu-link-implementation","Custom Menu Link Implementation",[11,577,578,579,584,585,587,588,590],{},"Now your app needs to do the same request dance as the Custom Page. You can view that process ",[15,580,583],{"href":581,"rel":582},"https:\u002F\u002Fgithub.com\u002Fcbnsndwch\u002Fghl-app-template#sso-iframe-integration",[19],"here",". Remember if you have changed the ",[78,586,163],{}," or ",[78,589,518],{}," message names here, they need to be changed in your app implementation too.",[35,592,594],{"id":593},"the-code","The Code",[11,596,597],{},"Here's the full code",[103,599,601],{"className":105,"code":600,"language":107,"meta":108,"style":108},"const appId = \"123\";\nconst locationId = \"abc\";\n\nwindow.addEventListener(\"message\", async ({ data }) => {\n  \u002F\u002F Make sure this is a request for user data\n  if (data.message === \"REQUEST_USER_DATA\") {\n    \u002F\u002F Build request headers\n    const requestHeaders = new Headers();\n    requestHeaders.append(\"source\", \"WEB_USER\");\n    requestHeaders.append(\"version\", \"2021-07-28\");\n    requestHeaders.append(\"accept\", \"application\u002Fjson, text\u002Fplain, *\u002F*\");\n    requestHeaders.append(\"channel\", \"APP\");\n    requestHeaders.append(\"location\", locationId);\n    requestHeaders.append(\"token-id\", await window.getToken());\n\n    \u002F\u002F Request session key from GHL endpoint\n    const sessionDetailsResponse = await fetch(`https:\u002F\u002Fservices.leadconnectorhq.com\u002Foauth\u002Fsso\u002F${appId}\u002Fsession-details`, {\n      method: \"GET\",\n      headers: requestHeaders,\n    });\n\n    \u002F\u002F Get JSON payload from request\n    const sessionDetails = await sessionDetailsResponse.json();\n\n    \u002F\u002F Find the iFrame\n    const frame = document.querySelector(\".custom-link iframe\");\n\n    \u002F\u002F Respond to iFrame with data\n    frame.contentWindow.postMessage(\n      {\n        message: \"REQUEST_USER_DATA_RESPONSE\",\n        payload: sessionDetails.data,\n      },\n      \"*\"\n    );\n  }\n});\n",[78,602,603,618,632,638,666,671,684,689,705,723,740,757,774,787,808,813,819,842,852,858,864,869,875,892,897,903,922,927,933,943,949,959,965,971,977,983,989],{"__ignoreMap":108},[112,604,605,607,610,612,615],{"class":114,"line":115},[112,606,216],{"class":136},[112,608,609],{"class":219}," appId",[112,611,223],{"class":136},[112,613,614],{"class":129}," \"123\"",[112,616,617],{"class":118},";\n",[112,619,620,622,625,627,630],{"class":114,"line":189},[112,621,216],{"class":136},[112,623,624],{"class":219}," locationId",[112,626,223],{"class":136},[112,628,629],{"class":129}," \"abc\"",[112,631,617],{"class":118},[112,633,634],{"class":114,"line":196},[112,635,637],{"emptyLinePlaceholder":636},true,"\n",[112,639,640,642,644,646,648,650,652,655,657,660,663],{"class":114,"line":274},[112,641,119],{"class":118},[112,643,123],{"class":122},[112,645,126],{"class":118},[112,647,130],{"class":129},[112,649,133],{"class":118},[112,651,84],{"class":136},[112,653,654],{"class":118}," ({ ",[112,656,100],{"class":145},[112,658,659],{"class":118}," }) ",[112,661,662],{"class":136},"=>",[112,664,665],{"class":118}," {\n",[112,667,668],{"class":114,"line":293},[112,669,670],{"class":192},"  \u002F\u002F Make sure this is a request for user data\n",[112,672,673,676,678,680,682],{"class":114,"line":563},[112,674,675],{"class":136},"  if",[112,677,177],{"class":118},[112,679,180],{"class":136},[112,681,183],{"class":129},[112,683,186],{"class":118},[112,685,686],{"class":114,"line":569},[112,687,688],{"class":192},"    \u002F\u002F Build request headers\n",[112,690,692,695,697,699,701,703],{"class":114,"line":691},8,[112,693,694],{"class":136},"    const",[112,696,220],{"class":219},[112,698,223],{"class":136},[112,700,226],{"class":136},[112,702,229],{"class":122},[112,704,232],{"class":118},[112,706,708,711,713,715,717,719,721],{"class":114,"line":707},9,[112,709,710],{"class":118},"    requestHeaders.",[112,712,240],{"class":122},[112,714,126],{"class":118},[112,716,245],{"class":129},[112,718,133],{"class":118},[112,720,250],{"class":129},[112,722,253],{"class":118},[112,724,726,728,730,732,734,736,738],{"class":114,"line":725},10,[112,727,710],{"class":118},[112,729,240],{"class":122},[112,731,126],{"class":118},[112,733,264],{"class":129},[112,735,133],{"class":118},[112,737,269],{"class":129},[112,739,253],{"class":118},[112,741,743,745,747,749,751,753,755],{"class":114,"line":742},11,[112,744,710],{"class":118},[112,746,240],{"class":122},[112,748,126],{"class":118},[112,750,283],{"class":129},[112,752,133],{"class":118},[112,754,288],{"class":129},[112,756,253],{"class":118},[112,758,760,762,764,766,768,770,772],{"class":114,"line":759},12,[112,761,710],{"class":118},[112,763,240],{"class":122},[112,765,126],{"class":118},[112,767,302],{"class":129},[112,769,133],{"class":118},[112,771,307],{"class":129},[112,773,253],{"class":118},[112,775,777,779,781,783,785],{"class":114,"line":776},13,[112,778,710],{"class":118},[112,780,240],{"class":122},[112,782,126],{"class":118},[112,784,365],{"class":129},[112,786,368],{"class":118},[112,788,790,792,794,796,798,800,802,804,806],{"class":114,"line":789},14,[112,791,710],{"class":118},[112,793,240],{"class":122},[112,795,126],{"class":118},[112,797,332],{"class":129},[112,799,133],{"class":118},[112,801,337],{"class":136},[112,803,340],{"class":118},[112,805,343],{"class":122},[112,807,346],{"class":118},[112,809,811],{"class":114,"line":810},15,[112,812,637],{"emptyLinePlaceholder":636},[112,814,816],{"class":114,"line":815},16,[112,817,818],{"class":192},"    \u002F\u002F Request session key from GHL endpoint\n",[112,820,822,824,826,828,830,832,834,836,838,840],{"class":114,"line":821},17,[112,823,694],{"class":136},[112,825,391],{"class":219},[112,827,223],{"class":136},[112,829,451],{"class":136},[112,831,396],{"class":122},[112,833,126],{"class":118},[112,835,401],{"class":129},[112,837,404],{"class":118},[112,839,407],{"class":129},[112,841,410],{"class":118},[112,843,845,848,850],{"class":114,"line":844},18,[112,846,847],{"class":118},"      method: ",[112,849,418],{"class":129},[112,851,421],{"class":118},[112,853,855],{"class":114,"line":854},19,[112,856,857],{"class":118},"      headers: requestHeaders,\n",[112,859,861],{"class":114,"line":860},20,[112,862,863],{"class":118},"    });\n",[112,865,867],{"class":114,"line":866},21,[112,868,637],{"emptyLinePlaceholder":636},[112,870,872],{"class":114,"line":871},22,[112,873,874],{"class":192},"    \u002F\u002F Get JSON payload from request\n",[112,876,878,880,882,884,886,888,890],{"class":114,"line":877},23,[112,879,694],{"class":136},[112,881,446],{"class":219},[112,883,223],{"class":136},[112,885,451],{"class":136},[112,887,454],{"class":118},[112,889,457],{"class":122},[112,891,232],{"class":118},[112,893,895],{"class":114,"line":894},24,[112,896,637],{"emptyLinePlaceholder":636},[112,898,900],{"class":114,"line":899},25,[112,901,902],{"class":192},"    \u002F\u002F Find the iFrame\n",[112,904,906,908,910,912,914,916,918,920],{"class":114,"line":905},26,[112,907,694],{"class":136},[112,909,482],{"class":219},[112,911,223],{"class":136},[112,913,487],{"class":118},[112,915,490],{"class":122},[112,917,126],{"class":118},[112,919,495],{"class":129},[112,921,253],{"class":118},[112,923,925],{"class":114,"line":924},27,[112,926,637],{"emptyLinePlaceholder":636},[112,928,930],{"class":114,"line":929},28,[112,931,932],{"class":192},"    \u002F\u002F Respond to iFrame with data\n",[112,934,936,939,941],{"class":114,"line":935},29,[112,937,938],{"class":118},"    frame.contentWindow.",[112,940,532],{"class":122},[112,942,535],{"class":118},[112,944,946],{"class":114,"line":945},30,[112,947,948],{"class":118},"      {\n",[112,950,952,955,957],{"class":114,"line":951},31,[112,953,954],{"class":118},"        message: ",[112,956,548],{"class":129},[112,958,421],{"class":118},[112,960,962],{"class":114,"line":961},32,[112,963,964],{"class":118},"        payload: sessionDetails.data,\n",[112,966,968],{"class":114,"line":967},33,[112,969,970],{"class":118},"      },\n",[112,972,974],{"class":114,"line":973},34,[112,975,976],{"class":129},"      \"*\"\n",[112,978,980],{"class":114,"line":979},35,[112,981,982],{"class":118},"    );\n",[112,984,986],{"class":114,"line":985},36,[112,987,988],{"class":118},"  }\n",[112,990,992],{"class":114,"line":991},37,[112,993,431],{"class":118},[995,996,997],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":108,"searchDepth":189,"depth":189,"links":999},[1000,1001,1007,1008],{"id":37,"depth":189,"text":38},{"id":58,"depth":189,"text":59,"children":1002},[1003,1004,1005,1006],{"id":66,"depth":196,"text":67},{"id":202,"depth":196,"text":203},{"id":371,"depth":196,"text":372},{"id":462,"depth":196,"text":463},{"id":574,"depth":189,"text":575},{"id":593,"depth":189,"text":594},"2023-11-22T12:00:00","You can use SSO sessions from inside Custom Menu Links, not just custom pages",null,"md",{"headline":6,"dateUpdated":108,"author":1014,"authorUrl":1015},"Adrian Gonzales","https:\u002F\u002Fvirexmachina.com\u002F","\u002Fblog\u002Fhigh-level-sso-custom-links",{"title":6,"description":1010},"blog\u002Fhigh-level-sso-custom-links","F_2_MjXJqLG3UwddGImJltHKLMSHEqIHE_rBkK_DONE",{"id":1021,"title":1022,"body":1023,"date":1009,"description":1221,"excerpt":1011,"extension":1012,"meta":1222,"navigation":636,"path":1224,"seo":1225,"stem":1226,"__hash__":1227},"blog\u002Fblog\u002Fhigh-level-simple-contact-creation.md","HighLevel: Simple contact creation with a POST operation",{"type":8,"value":1024,"toc":1217},[1025,1034,1043,1049,1053,1059,1063,1067,1070,1075,1078,1085,1114,1117,1214],[11,1026,1027,1028,1033],{},"HighLevel has an ",[15,1029,1032],{"href":1030,"rel":1031},"https:\u002F\u002Fhighlevel.stoplight.io\u002Fdocs\u002Fintegrations\u002F0443d7d1a4bd0-overview",[19],"API"," with lots of features for integration with your applications and processes. But sometimes you just need to add a Contact from an application, like a Web Form.",[11,1035,1036,1037,1042],{},"HighLevel provides a ",[15,1038,1041],{"href":1039,"rel":1040},"https:\u002F\u002Fhelp.gohighlevel.com\u002Fsupport\u002Fsolutions\u002Ffolders\u002F48000666021",[19],"Zapier integration"," that can fit a lot of needs. If your requirements push you towards a custom integration, thankfully you can use the same endpoint for your needs as well.",[1044,1045,1048],"div",{"className":1046},[1047],"note-block","\nNote: this only works with Sub Accounts. Agencies need to use the full API.\n",[35,1050,1052],{"id":1051},"create-key","Create Key",[11,1054,1055,1056,33],{},"Your API key can be found and\u002For regenerated in the Location view at Settings > Business Profile > General Information section. At the bottom there's a section labeled ",[512,1057,1058],{},"API Key",[1044,1060,1062],{"className":1061},[1047],"\nNote: Regenerating the API key (clicking the refresh icon) will invalidate any existing integration using the key.\n",[35,1064,1066],{"id":1065},"perform-post-request","Perform POST request",[11,1068,1069],{},"To create a new contact, perform a POST request to this URL:",[11,1071,1072],{},[78,1073,1074],{},"https:\u002F\u002Fapi.gohighlevel.com\u002Fzapier\u002Fcontact\u002Fadd_update\u002F",[11,1076,1077],{},"You need headers determining the content type, and the authorization Bearer Token.",[11,1079,1080,1081,1084],{},"This should be the key you generated in the UI, prefixed with ",[78,1082,1083],{},"Bearer ",", space included.",[103,1086,1090],{"className":1087,"code":1088,"language":1089,"meta":108,"style":108},"language-yaml shiki shiki-themes github-light github-dark","Authorization: \"Bearer apiKey123\"\nContent-Type: \"application\u002Fjson\"\n","yaml",[78,1091,1092,1104],{"__ignoreMap":108},[112,1093,1094,1098,1101],{"class":114,"line":115},[112,1095,1097],{"class":1096},"s9eBZ","Authorization",[112,1099,1100],{"class":118},": ",[112,1102,1103],{"class":129},"\"Bearer apiKey123\"\n",[112,1105,1106,1109,1111],{"class":114,"line":189},[112,1107,1108],{"class":1096},"Content-Type",[112,1110,1100],{"class":118},[112,1112,1113],{"class":129},"\"application\u002Fjson\"\n",[11,1115,1116],{},"With a JSON body with these parameters:",[103,1118,1121],{"className":1119,"code":1120,"language":457,"meta":108,"style":108},"language-json shiki shiki-themes github-light github-dark","{\n  \"first_name\": \"Some First Name\",\n  \"last_name\": \"Some Last Name\",\n  \"email\": \"name@example.com\",\n  \"phone\": \"+112355500000\",\n  \"source\": \"Your App Name\",\n  \"tags\": \"tag1,tag2,tag3\",\n  \"lead\": true\n}\n",[78,1122,1123,1128,1140,1152,1164,1176,1188,1200,1210],{"__ignoreMap":108},[112,1124,1125],{"class":114,"line":115},[112,1126,1127],{"class":118},"{\n",[112,1129,1130,1133,1135,1138],{"class":114,"line":189},[112,1131,1132],{"class":219},"  \"first_name\"",[112,1134,1100],{"class":118},[112,1136,1137],{"class":129},"\"Some First Name\"",[112,1139,421],{"class":118},[112,1141,1142,1145,1147,1150],{"class":114,"line":196},[112,1143,1144],{"class":219},"  \"last_name\"",[112,1146,1100],{"class":118},[112,1148,1149],{"class":129},"\"Some Last Name\"",[112,1151,421],{"class":118},[112,1153,1154,1157,1159,1162],{"class":114,"line":274},[112,1155,1156],{"class":219},"  \"email\"",[112,1158,1100],{"class":118},[112,1160,1161],{"class":129},"\"name@example.com\"",[112,1163,421],{"class":118},[112,1165,1166,1169,1171,1174],{"class":114,"line":293},[112,1167,1168],{"class":219},"  \"phone\"",[112,1170,1100],{"class":118},[112,1172,1173],{"class":129},"\"+112355500000\"",[112,1175,421],{"class":118},[112,1177,1178,1181,1183,1186],{"class":114,"line":563},[112,1179,1180],{"class":219},"  \"source\"",[112,1182,1100],{"class":118},[112,1184,1185],{"class":129},"\"Your App Name\"",[112,1187,421],{"class":118},[112,1189,1190,1193,1195,1198],{"class":114,"line":569},[112,1191,1192],{"class":219},"  \"tags\"",[112,1194,1100],{"class":118},[112,1196,1197],{"class":129},"\"tag1,tag2,tag3\"",[112,1199,421],{"class":118},[112,1201,1202,1205,1207],{"class":114,"line":691},[112,1203,1204],{"class":219},"  \"lead\"",[112,1206,1100],{"class":118},[112,1208,1209],{"class":219},"true\n",[112,1211,1212],{"class":114,"line":707},[112,1213,199],{"class":118},[995,1215,1216],{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":108,"searchDepth":189,"depth":189,"links":1218},[1219,1220],{"id":1051,"depth":189,"text":1052},{"id":1065,"depth":189,"text":1066},"Creating contacts in HighLevel using the Zapier API",{"headline":1223,"dateUpdated":108,"author":1014,"authorUrl":1015},"HighLevel: Simple Contact creation a POST operation","\u002Fblog\u002Fhigh-level-simple-contact-creation",{"title":1022,"description":1221},"blog\u002Fhigh-level-simple-contact-creation","-FIsufXZZ0cSv3EenUMJqJk9GZx5dkLT6BmMzHiaAn4",{"id":1229,"title":1230,"body":1231,"date":2269,"description":2270,"excerpt":1011,"extension":1012,"meta":2271,"navigation":636,"path":2272,"seo":2273,"stem":2274,"__hash__":2275},"blog\u002Fblog\u002Fhigh-level-decrypting-sso-sessions-with-php.md","HighLevel: Decrypting SSO sessions with PHP",{"type":8,"value":1232,"toc":2265},[1233,1238,1242,1245,1264,1273,1288,1303,1327,1330,1333,2233,2236,2259,2262],[11,1234,13,1235,21],{},[15,1236,20],{"href":17,"rel":1237},[19],[64,1239,1241],{"id":1240},"the-challenge","The Challenge",[11,1243,1244],{},"This process is an interesting challenge due to security concerns and cross-domain resources loaded in iFrames.",[11,1246,1247,1248,1253,1254,1257,1258,1263],{},"The official documentation around this feature is very limited, mostly ",[15,1249,1252],{"href":1250,"rel":1251},"https:\u002F\u002Fgithub.com\u002FGoHighLevel\u002Fghl-marketplace-app-template#sso",[19],"this example implementation",". Thankfully, talented people are sharing their knowledge, like ",[15,1255,32],{"href":30,"rel":1256},[19]," and ",[15,1259,1262],{"href":1260,"rel":1261},"https:\u002F\u002Fseankerr.dev\u002Fposts\u002Fdecrypting-highlevel-sso-session-using-python\u002F",[19],"Sean Kerr's blog post adapting the process to Python",". The obvious challenge was implementing the decryption logic in PHP.",[11,1265,1266,1267,1272],{},"HighLevel uses ",[15,1268,1271],{"href":1269,"rel":1270},"https:\u002F\u002Fgithub.com\u002Fbrix\u002Fcrypto-js",[19],"CryptoJS"," to encode the SSO payload. Unfortunately, as Sean found out, CryptoJS's default implementation does not use a standard approach to encryption.",[46,1274,1275,1278],{},[11,1276,1277],{},"The crux of the problem is two parts:",[1279,1280,1281,1285],"ul",{},[1282,1283,1284],"li",{},"CryptoJS is using the same prefix as OpenSSL when encrypting AES data.",[1282,1286,1287],{},"It’s using MD5 instead of SHA256 as a hashing mechanism, and it only does a single iteration instead of thousands (more secure).",[11,1289,1290,1291,1296,1297,1302],{},"Sean's post showed me why neither ",[15,1292,1295],{"href":1293,"rel":1294},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F10.x\u002Fencryption",[19],"Laravel's Encryption service"," nor a basic ",[15,1298,1301],{"href":1299,"rel":1300},"https:\u002F\u002Fwww.php.net\u002Fmanual\u002Fen\u002Ffunction.openssl-decrypt.php",[19],"OpenSSL decrypt"," implementation were working. After some rounds of interpreting their Python example to PHP, I ended up back at Google assuming someone somewhere had run into this problem before.",[11,1304,1305,1306,1311,1312,133,1315,1318,1319,1322,1323,1326],{},"As usual, StackOverflow came to the rescue, with a question aptly titled ",[15,1307,1310],{"href":1308,"rel":1309},"https:\u002F\u002Fstackoverflow.com\u002Fa\u002F65235352",[19],"Decrypt Crypto-js encrypted text with key with PHP",". The answer's code showed PHP's ",[78,1313,1314],{},"hash_init",[78,1316,1317],{},"hash_update",", and ",[78,1320,1321],{},"hash_final"," methods matched the ",[78,1324,1325],{},"hasher"," update and digest functionality in Python.",[64,1328,1329],{"id":78},"Code",[11,1331,1332],{},"Here's the functionality, cleaned up a little and implemented as a class. I avoided using parameter and return types for increased compatibility.",[103,1334,1336],{"className":105,"code":1335,"language":107,"meta":108,"style":108},"\u002F**\n * CryptoJS compatibility module\n *\u002F\nclass CryptoJs\n{\n    \u002F**\n     * Decrypt a payload encrypted with a passphrase\n     *\n     * @see https:\u002F\u002Fstackoverflow.com\u002Fa\u002F65235352\n     *\n     * @param string $payload\n     * @param string $passphrase\n     * @return string\n     *\u002F\n    public static function decrypt($payload, $passphrase)\n    {\n        $ciphertext = base64_decode($payload);\n\n        if (substr($ciphertext, 0, 8) != \"Salted__\") {\n            return false;\n        }\n\n        $salt = substr($ciphertext, 8, 8);\n\n        $keyAndIV = self::deriveKeyAndIv($passphrase, $salt);\n\n        return openssl_decrypt(\n            substr($ciphertext, 16),\n            \"aes-256-cbc\",\n            $keyAndIV[\"key\"],\n            OPENSSL_RAW_DATA, \u002F\u002F base64 was already decoded\n            $keyAndIV[\"iv\"]\n        );\n    }\n\n    \u002F**\n     * Derive key from the given passphrase and salt.\n     *\n     * @param string $passphrase\n     * @param string $salt\n     * @param integer $keySize\n     * @param integer $ivSize\n     * @param integer $iterations\n     * @param string $hashAlgorithm\n     * @return void\n     *\u002F\n    private static function deriveKeyAndIv($passphrase, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = \"md5\")\n    {\n        $targetKeySize = $keySize + $ivSize;\n        $derivedBytes = \"\";\n        $numberOfDerivedWords = 0;\n        $block = NULL;\n        $hasher = hash_init($hashAlgorithm);\n\n        while ($numberOfDerivedWords \u003C $targetKeySize) {\n            if ($block != NULL) {\n                hash_update($hasher, $block);\n            }\n\n            hash_update($hasher, $passphrase);\n            hash_update($hasher, $salt);\n            $block = hash_final($hasher, TRUE);\n            $hasher = hash_init($hashAlgorithm);\n\n            \u002F\u002F Iterations\n            for ($i = 1; $i \u003C $iterations; $i++) {\n                hash_update($hasher, $block);\n                $block = hash_final($hasher, TRUE);\n                $hasher = hash_init($hashAlgorithm);\n            }\n\n            $derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));\n            $numberOfDerivedWords += strlen($block) \u002F 4;\n        }\n\n        return array(\n            \"key\" => substr($derivedBytes, 0, $keySize * 4),\n            \"iv\"  => substr($derivedBytes, $keySize * 4, $ivSize * 4)\n        );\n    }\n}\n",[78,1337,1338,1343,1348,1353,1361,1365,1370,1375,1380,1391,1395,1408,1419,1429,1434,1460,1465,1479,1483,1516,1526,1531,1535,1555,1559,1578,1582,1592,1605,1612,1623,1633,1643,1648,1653,1657,1661,1666,1671,1682,1694,1707,1719,1731,1743,1753,1758,1822,1827,1844,1857,1870,1883,1897,1902,1917,1932,1941,1947,1952,1961,1969,1988,2000,2005,2011,2037,2044,2060,2072,2077,2082,2124,2146,2151,2156,2166,2191,2218,2223,2228],{"__ignoreMap":108},[112,1339,1340],{"class":114,"line":115},[112,1341,1342],{"class":192},"\u002F**\n",[112,1344,1345],{"class":114,"line":189},[112,1346,1347],{"class":192}," * CryptoJS compatibility module\n",[112,1349,1350],{"class":114,"line":196},[112,1351,1352],{"class":192}," *\u002F\n",[112,1354,1355,1358],{"class":114,"line":274},[112,1356,1357],{"class":136},"class",[112,1359,1360],{"class":122}," CryptoJs\n",[112,1362,1363],{"class":114,"line":293},[112,1364,1127],{"class":118},[112,1366,1367],{"class":114,"line":563},[112,1368,1369],{"class":192},"    \u002F**\n",[112,1371,1372],{"class":114,"line":569},[112,1373,1374],{"class":192},"     * Decrypt a payload encrypted with a passphrase\n",[112,1376,1377],{"class":114,"line":691},[112,1378,1379],{"class":192},"     *\n",[112,1381,1382,1385,1388],{"class":114,"line":707},[112,1383,1384],{"class":192},"     * ",[112,1386,1387],{"class":136},"@see",[112,1389,1390],{"class":118}," https:\u002F\u002Fstackoverflow.com\u002Fa\u002F65235352\n",[112,1392,1393],{"class":114,"line":725},[112,1394,1379],{"class":192},[112,1396,1397,1399,1402,1405],{"class":114,"line":742},[112,1398,1384],{"class":192},[112,1400,1401],{"class":136},"@param",[112,1403,1404],{"class":118}," string",[112,1406,1407],{"class":192}," $payload\n",[112,1409,1410,1412,1414,1416],{"class":114,"line":759},[112,1411,1384],{"class":192},[112,1413,1401],{"class":136},[112,1415,1404],{"class":118},[112,1417,1418],{"class":192}," $passphrase\n",[112,1420,1421,1423,1426],{"class":114,"line":776},[112,1422,1384],{"class":192},[112,1424,1425],{"class":136},"@return",[112,1427,1428],{"class":192}," string\n",[112,1430,1431],{"class":114,"line":789},[112,1432,1433],{"class":192},"     *\u002F\n",[112,1435,1436,1439,1442,1444,1447,1449,1452,1454,1457],{"class":114,"line":810},[112,1437,1438],{"class":136},"    public",[112,1440,1441],{"class":136}," static",[112,1443,139],{"class":136},[112,1445,1446],{"class":122}," decrypt",[112,1448,126],{"class":118},[112,1450,1451],{"class":145},"$payload",[112,1453,133],{"class":118},[112,1455,1456],{"class":145},"$passphrase",[112,1458,1459],{"class":118},")\n",[112,1461,1462],{"class":114,"line":815},[112,1463,1464],{"class":118},"    {\n",[112,1466,1467,1470,1473,1476],{"class":114,"line":821},[112,1468,1469],{"class":118},"        $ciphertext ",[112,1471,1472],{"class":136},"=",[112,1474,1475],{"class":122}," base64_decode",[112,1477,1478],{"class":118},"($payload);\n",[112,1480,1481],{"class":114,"line":844},[112,1482,637],{"emptyLinePlaceholder":636},[112,1484,1485,1488,1491,1494,1497,1500,1502,1505,1508,1511,1514],{"class":114,"line":854},[112,1486,1487],{"class":136},"        if",[112,1489,1490],{"class":118}," (",[112,1492,1493],{"class":122},"substr",[112,1495,1496],{"class":118},"($ciphertext, ",[112,1498,1499],{"class":219},"0",[112,1501,133],{"class":118},[112,1503,1504],{"class":219},"8",[112,1506,1507],{"class":118},") ",[112,1509,1510],{"class":136},"!=",[112,1512,1513],{"class":129}," \"Salted__\"",[112,1515,186],{"class":118},[112,1517,1518,1521,1524],{"class":114,"line":860},[112,1519,1520],{"class":136},"            return",[112,1522,1523],{"class":219}," false",[112,1525,617],{"class":118},[112,1527,1528],{"class":114,"line":866},[112,1529,1530],{"class":118},"        }\n",[112,1532,1533],{"class":114,"line":871},[112,1534,637],{"emptyLinePlaceholder":636},[112,1536,1537,1540,1542,1545,1547,1549,1551,1553],{"class":114,"line":877},[112,1538,1539],{"class":118},"        $salt ",[112,1541,1472],{"class":136},[112,1543,1544],{"class":122}," substr",[112,1546,1496],{"class":118},[112,1548,1504],{"class":219},[112,1550,133],{"class":118},[112,1552,1504],{"class":219},[112,1554,253],{"class":118},[112,1556,1557],{"class":114,"line":894},[112,1558,637],{"emptyLinePlaceholder":636},[112,1560,1561,1564,1566,1569,1572,1575],{"class":114,"line":899},[112,1562,1563],{"class":118},"        $keyAndIV ",[112,1565,1472],{"class":136},[112,1567,1568],{"class":122}," self",[112,1570,1571],{"class":118},"::",[112,1573,1574],{"class":122},"deriveKeyAndIv",[112,1576,1577],{"class":118},"($passphrase, $salt);\n",[112,1579,1580],{"class":114,"line":905},[112,1581,637],{"emptyLinePlaceholder":636},[112,1583,1584,1587,1590],{"class":114,"line":924},[112,1585,1586],{"class":136},"        return",[112,1588,1589],{"class":122}," openssl_decrypt",[112,1591,535],{"class":118},[112,1593,1594,1597,1599,1602],{"class":114,"line":929},[112,1595,1596],{"class":122},"            substr",[112,1598,1496],{"class":118},[112,1600,1601],{"class":219},"16",[112,1603,1604],{"class":118},"),\n",[112,1606,1607,1610],{"class":114,"line":935},[112,1608,1609],{"class":129},"            \"aes-256-cbc\"",[112,1611,421],{"class":118},[112,1613,1614,1617,1620],{"class":114,"line":945},[112,1615,1616],{"class":118},"            $keyAndIV[",[112,1618,1619],{"class":129},"\"key\"",[112,1621,1622],{"class":118},"],\n",[112,1624,1625,1628,1630],{"class":114,"line":951},[112,1626,1627],{"class":219},"            OPENSSL_RAW_DATA",[112,1629,133],{"class":118},[112,1631,1632],{"class":192},"\u002F\u002F base64 was already decoded\n",[112,1634,1635,1637,1640],{"class":114,"line":961},[112,1636,1616],{"class":118},[112,1638,1639],{"class":129},"\"iv\"",[112,1641,1642],{"class":118},"]\n",[112,1644,1645],{"class":114,"line":967},[112,1646,1647],{"class":118},"        );\n",[112,1649,1650],{"class":114,"line":973},[112,1651,1652],{"class":118},"    }\n",[112,1654,1655],{"class":114,"line":979},[112,1656,637],{"emptyLinePlaceholder":636},[112,1658,1659],{"class":114,"line":985},[112,1660,1369],{"class":192},[112,1662,1663],{"class":114,"line":991},[112,1664,1665],{"class":192},"     * Derive key from the given passphrase and salt.\n",[112,1667,1669],{"class":114,"line":1668},38,[112,1670,1379],{"class":192},[112,1672,1674,1676,1678,1680],{"class":114,"line":1673},39,[112,1675,1384],{"class":192},[112,1677,1401],{"class":136},[112,1679,1404],{"class":118},[112,1681,1418],{"class":192},[112,1683,1685,1687,1689,1691],{"class":114,"line":1684},40,[112,1686,1384],{"class":192},[112,1688,1401],{"class":136},[112,1690,1404],{"class":118},[112,1692,1693],{"class":192}," $salt\n",[112,1695,1697,1699,1701,1704],{"class":114,"line":1696},41,[112,1698,1384],{"class":192},[112,1700,1401],{"class":136},[112,1702,1703],{"class":118}," integer",[112,1705,1706],{"class":192}," $keySize\n",[112,1708,1710,1712,1714,1716],{"class":114,"line":1709},42,[112,1711,1384],{"class":192},[112,1713,1401],{"class":136},[112,1715,1703],{"class":118},[112,1717,1718],{"class":192}," $ivSize\n",[112,1720,1722,1724,1726,1728],{"class":114,"line":1721},43,[112,1723,1384],{"class":192},[112,1725,1401],{"class":136},[112,1727,1703],{"class":118},[112,1729,1730],{"class":192}," $iterations\n",[112,1732,1734,1736,1738,1740],{"class":114,"line":1733},44,[112,1735,1384],{"class":192},[112,1737,1401],{"class":136},[112,1739,1404],{"class":118},[112,1741,1742],{"class":192}," $hashAlgorithm\n",[112,1744,1746,1748,1750],{"class":114,"line":1745},45,[112,1747,1384],{"class":192},[112,1749,1425],{"class":136},[112,1751,1752],{"class":192}," void\n",[112,1754,1756],{"class":114,"line":1755},46,[112,1757,1433],{"class":192},[112,1759,1761,1764,1766,1768,1771,1773,1775,1777,1780,1782,1785,1787,1790,1792,1795,1797,1800,1802,1805,1807,1810,1812,1815,1817,1820],{"class":114,"line":1760},47,[112,1762,1763],{"class":136},"    private",[112,1765,1441],{"class":136},[112,1767,139],{"class":136},[112,1769,1770],{"class":122}," deriveKeyAndIv",[112,1772,126],{"class":118},[112,1774,1456],{"class":145},[112,1776,133],{"class":118},[112,1778,1779],{"class":145},"$salt",[112,1781,133],{"class":118},[112,1783,1784],{"class":145},"$keySize",[112,1786,223],{"class":136},[112,1788,1789],{"class":219}," 8",[112,1791,133],{"class":118},[112,1793,1794],{"class":145},"$ivSize",[112,1796,223],{"class":136},[112,1798,1799],{"class":219}," 4",[112,1801,133],{"class":118},[112,1803,1804],{"class":145},"$iterations",[112,1806,223],{"class":136},[112,1808,1809],{"class":219}," 1",[112,1811,133],{"class":118},[112,1813,1814],{"class":145},"$hashAlgorithm",[112,1816,223],{"class":136},[112,1818,1819],{"class":129}," \"md5\"",[112,1821,1459],{"class":118},[112,1823,1825],{"class":114,"line":1824},48,[112,1826,1464],{"class":118},[112,1828,1830,1833,1835,1838,1841],{"class":114,"line":1829},49,[112,1831,1832],{"class":118},"        $targetKeySize ",[112,1834,1472],{"class":136},[112,1836,1837],{"class":118}," $keySize ",[112,1839,1840],{"class":136},"+",[112,1842,1843],{"class":118}," $ivSize;\n",[112,1845,1847,1850,1852,1855],{"class":114,"line":1846},50,[112,1848,1849],{"class":118},"        $derivedBytes ",[112,1851,1472],{"class":136},[112,1853,1854],{"class":129}," \"\"",[112,1856,617],{"class":118},[112,1858,1860,1863,1865,1868],{"class":114,"line":1859},51,[112,1861,1862],{"class":118},"        $numberOfDerivedWords ",[112,1864,1472],{"class":136},[112,1866,1867],{"class":219}," 0",[112,1869,617],{"class":118},[112,1871,1873,1876,1878,1881],{"class":114,"line":1872},52,[112,1874,1875],{"class":118},"        $block ",[112,1877,1472],{"class":136},[112,1879,1880],{"class":219}," NULL",[112,1882,617],{"class":118},[112,1884,1886,1889,1891,1894],{"class":114,"line":1885},53,[112,1887,1888],{"class":118},"        $hasher ",[112,1890,1472],{"class":136},[112,1892,1893],{"class":122}," hash_init",[112,1895,1896],{"class":118},"($hashAlgorithm);\n",[112,1898,1900],{"class":114,"line":1899},54,[112,1901,637],{"emptyLinePlaceholder":636},[112,1903,1905,1908,1911,1914],{"class":114,"line":1904},55,[112,1906,1907],{"class":136},"        while",[112,1909,1910],{"class":118}," ($numberOfDerivedWords ",[112,1912,1913],{"class":136},"\u003C",[112,1915,1916],{"class":118}," $targetKeySize) {\n",[112,1918,1920,1923,1926,1928,1930],{"class":114,"line":1919},56,[112,1921,1922],{"class":136},"            if",[112,1924,1925],{"class":118}," ($block ",[112,1927,1510],{"class":136},[112,1929,1880],{"class":219},[112,1931,186],{"class":118},[112,1933,1935,1938],{"class":114,"line":1934},57,[112,1936,1937],{"class":122},"                hash_update",[112,1939,1940],{"class":118},"($hasher, $block);\n",[112,1942,1944],{"class":114,"line":1943},58,[112,1945,1946],{"class":118},"            }\n",[112,1948,1950],{"class":114,"line":1949},59,[112,1951,637],{"emptyLinePlaceholder":636},[112,1953,1955,1958],{"class":114,"line":1954},60,[112,1956,1957],{"class":122},"            hash_update",[112,1959,1960],{"class":118},"($hasher, $passphrase);\n",[112,1962,1964,1966],{"class":114,"line":1963},61,[112,1965,1957],{"class":122},[112,1967,1968],{"class":118},"($hasher, $salt);\n",[112,1970,1972,1975,1977,1980,1983,1986],{"class":114,"line":1971},62,[112,1973,1974],{"class":118},"            $block ",[112,1976,1472],{"class":136},[112,1978,1979],{"class":122}," hash_final",[112,1981,1982],{"class":118},"($hasher, ",[112,1984,1985],{"class":219},"TRUE",[112,1987,253],{"class":118},[112,1989,1991,1994,1996,1998],{"class":114,"line":1990},63,[112,1992,1993],{"class":118},"            $hasher ",[112,1995,1472],{"class":136},[112,1997,1893],{"class":122},[112,1999,1896],{"class":118},[112,2001,2003],{"class":114,"line":2002},64,[112,2004,637],{"emptyLinePlaceholder":636},[112,2006,2008],{"class":114,"line":2007},65,[112,2009,2010],{"class":192},"            \u002F\u002F Iterations\n",[112,2012,2014,2017,2020,2022,2024,2027,2029,2032,2035],{"class":114,"line":2013},66,[112,2015,2016],{"class":136},"            for",[112,2018,2019],{"class":118}," ($i ",[112,2021,1472],{"class":136},[112,2023,1809],{"class":219},[112,2025,2026],{"class":118},"; $i ",[112,2028,1913],{"class":136},[112,2030,2031],{"class":118}," $iterations; $i",[112,2033,2034],{"class":136},"++",[112,2036,186],{"class":118},[112,2038,2040,2042],{"class":114,"line":2039},67,[112,2041,1937],{"class":122},[112,2043,1940],{"class":118},[112,2045,2047,2050,2052,2054,2056,2058],{"class":114,"line":2046},68,[112,2048,2049],{"class":118},"                $block ",[112,2051,1472],{"class":136},[112,2053,1979],{"class":122},[112,2055,1982],{"class":118},[112,2057,1985],{"class":219},[112,2059,253],{"class":118},[112,2061,2063,2066,2068,2070],{"class":114,"line":2062},69,[112,2064,2065],{"class":118},"                $hasher ",[112,2067,1472],{"class":136},[112,2069,1893],{"class":122},[112,2071,1896],{"class":118},[112,2073,2075],{"class":114,"line":2074},70,[112,2076,1946],{"class":118},[112,2078,2080],{"class":114,"line":2079},71,[112,2081,637],{"emptyLinePlaceholder":636},[112,2083,2085,2088,2090,2092,2095,2097,2099,2102,2104,2107,2110,2113,2116,2119,2121],{"class":114,"line":2084},72,[112,2086,2087],{"class":118},"            $derivedBytes .",[112,2089,1472],{"class":136},[112,2091,1544],{"class":122},[112,2093,2094],{"class":118},"($block, ",[112,2096,1499],{"class":219},[112,2098,133],{"class":118},[112,2100,2101],{"class":122},"min",[112,2103,126],{"class":118},[112,2105,2106],{"class":122},"strlen",[112,2108,2109],{"class":118},"($block), ($targetKeySize ",[112,2111,2112],{"class":136},"-",[112,2114,2115],{"class":118}," $numberOfDerivedWords) ",[112,2117,2118],{"class":136},"*",[112,2120,1799],{"class":219},[112,2122,2123],{"class":118},"));\n",[112,2125,2127,2130,2133,2136,2139,2142,2144],{"class":114,"line":2126},73,[112,2128,2129],{"class":118},"            $numberOfDerivedWords ",[112,2131,2132],{"class":136},"+=",[112,2134,2135],{"class":122}," strlen",[112,2137,2138],{"class":118},"($block) ",[112,2140,2141],{"class":136},"\u002F",[112,2143,1799],{"class":219},[112,2145,617],{"class":118},[112,2147,2149],{"class":114,"line":2148},74,[112,2150,1530],{"class":118},[112,2152,2154],{"class":114,"line":2153},75,[112,2155,637],{"emptyLinePlaceholder":636},[112,2157,2159,2161,2164],{"class":114,"line":2158},76,[112,2160,1586],{"class":136},[112,2162,2163],{"class":122}," array",[112,2165,535],{"class":118},[112,2167,2169,2172,2175,2177,2180,2182,2185,2187,2189],{"class":114,"line":2168},77,[112,2170,2171],{"class":129},"            \"key\"",[112,2173,2174],{"class":136}," =>",[112,2176,1544],{"class":122},[112,2178,2179],{"class":118},"($derivedBytes, ",[112,2181,1499],{"class":219},[112,2183,2184],{"class":118},", $keySize ",[112,2186,2118],{"class":136},[112,2188,1799],{"class":219},[112,2190,1604],{"class":118},[112,2192,2194,2197,2200,2202,2205,2207,2209,2212,2214,2216],{"class":114,"line":2193},78,[112,2195,2196],{"class":129},"            \"iv\"",[112,2198,2199],{"class":136},"  =>",[112,2201,1544],{"class":122},[112,2203,2204],{"class":118},"($derivedBytes, $keySize ",[112,2206,2118],{"class":136},[112,2208,1799],{"class":219},[112,2210,2211],{"class":118},", $ivSize ",[112,2213,2118],{"class":136},[112,2215,1799],{"class":219},[112,2217,1459],{"class":118},[112,2219,2221],{"class":114,"line":2220},79,[112,2222,1647],{"class":118},[112,2224,2226],{"class":114,"line":2225},80,[112,2227,1652],{"class":118},[112,2229,2231],{"class":114,"line":2230},81,[112,2232,199],{"class":118},[11,2234,2235],{},"This class can be used to decrypt the SSO payload like this:",[103,2237,2239],{"className":105,"code":2238,"language":107,"meta":108,"style":108},"$data = CryptoJs::decrypt($ssoPayload, $ssoPassphrase);\n",[78,2240,2241],{"__ignoreMap":108},[112,2242,2243,2246,2248,2251,2253,2256],{"class":114,"line":115},[112,2244,2245],{"class":118},"$data ",[112,2247,1472],{"class":136},[112,2249,2250],{"class":122}," CryptoJs",[112,2252,1571],{"class":118},[112,2254,2255],{"class":122},"decrypt",[112,2257,2258],{"class":118},"($ssoPayload, $ssoPassphrase);\n",[11,2260,2261],{},"Good luck on your implementation!",[995,2263,2264],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":108,"searchDepth":189,"depth":189,"links":2266},[2267,2268],{"id":1240,"depth":196,"text":1241},{"id":78,"depth":196,"text":1329},"2023-11-21T12:00:00","Using PHP to decrypt the HighLevel SSO payload",{"headline":1230,"dateUpdated":108,"author":1014,"authorUrl":1015},"\u002Fblog\u002Fhigh-level-decrypting-sso-sessions-with-php",{"title":1230,"description":2270},"blog\u002Fhigh-level-decrypting-sso-sessions-with-php","hr2bFLmYEsvRC09fMeP06ezTHE9MxAKmxg_Ma6dgmPY",{"id":2277,"title":2278,"body":2279,"date":2434,"description":2283,"excerpt":1011,"extension":1012,"meta":2435,"navigation":636,"path":2437,"seo":2438,"stem":2439,"__hash__":2440},"blog\u002Fblog\u002F2015-10-08-better-wordpress-cron-jobs-crontab-wpcli.md","Better Wordpress Cron Jobs with crontab and WP CLI",{"type":8,"value":2280,"toc":2429},[2281,2284,2287,2295,2299,2306,2310,2313,2316,2321,2324,2329,2332,2337,2340,2345,2351,2356,2363,2369,2374,2377,2380,2385,2396,2400,2407,2412,2418,2421,2426],[11,2282,2283],{},"WP Cron jobs can be powerful tools, allowing you to schedule tasks that need to happen automatically. Unfortunately, by default, this only runs on a page visit.",[11,2285,2286],{},"That means two things for you:",[1279,2288,2289,2292],{},[1282,2290,2291],{},"Jobs only run when there's visitors actively on the site when its supposed to run",[1282,2293,2294],{},"A log running job will hold up the page render for the unlucky visitors that trigger it",[64,2296,2298],{"id":2297},"so-how-do-you-do-that-better","So, how do you do that better?",[11,2300,2301,2302,2305],{},"Crontab is a great solution for running cron jobs, its what your server does for its own jobs. But getting it to execute tasks can be a little tricky. Theres workaround requiring your server to call ",[78,2303,2304],{},"curl http:\u002F\u002Fyoursite.com\u002Fwp-cron.php",", but that relies on curl and that domain is resolvable from the server.",[64,2307,2309],{"id":2308},"wp-cli-to-the-rescue","WP-CLI to the rescue",[11,2311,2312],{},"WP CLI has the ability to run cron jobs itself. It does this intelligently, obeying the set schedule even when its run outside the task's window. So that means we can safely run that command on a minute interval.",[11,2314,2315],{},"This command runs a cron job, but you notice, it needs the hook name and can only run one at a time",[11,2317,2318],{},[78,2319,2320],{},"wp cron event run cron_job_hook_name",[11,2322,2323],{},"This command lists all cron jobs available",[11,2325,2326],{},[78,2327,2328],{},"wp cron event list",[11,2330,2331],{},"So, we take that and only get the hook, no formatting.",[11,2333,2334],{},[78,2335,2336],{},"wp cron event list --fields=hook --format=ids",[11,2338,2339],{},"Then we're going to wrap that in a bash loop so that each hook that gets returned runs the cron job.",[11,2341,2342],{},[78,2343,2344],{},"for i in $(wp cron event list --fields=hook --format=ids); do wp cron event run $i; done",[11,2346,2347,2348],{},"That'll run them all! But since this is a cron job, it'll run outside our normal bash environment. So, we need to specify where the site is with: ",[78,2349,2350],{},"--path=\u002Ffull\u002Fpath\u002Fto\u002Fsite",[11,2352,2353],{},[78,2354,2355],{},"for i in $(wp cron event list --fields=hook --format=ids --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite); do wp cron event run $i --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite; done",[11,2357,2358,2359,2362],{},"Notice how we have to do that in the list command ",[512,2360,2361],{},"and"," the run command.",[11,2364,2365,2366],{},"Sometimes, depending on how you set up WP-CLI, you'll need to specify it's location too, like ",[78,2367,2368],{},"\u002Fusr\u002Flocal\u002Fbin\u002F",[11,2370,2371],{},[78,2372,2373],{},"for i in $(\u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event list --fields=hook --format=ids --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite); do \u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event run $i --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite; done",[11,2375,2376],{},"Awesome, so this should run great, lets put it in a cron job.",[11,2378,2379],{},"That runs all WP cron jobs, every 5 minutes (*\u002F12 is 60 minutes\u002F12 = 5 minutes)",[11,2381,2382],{},[78,2383,2384],{},"*\u002F12 * * * * for i in $(\u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event list --fields=hook --format=ids --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite); do \u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event run $i --quiet --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite; done",[11,2386,2387,2388,2391,2392,2395],{},"Note I added ",[78,2389,2390],{},"--quiet"," to the ",[78,2393,2394],{},"run"," command so that I don't get an email every time this runs.",[64,2397,2399],{"id":2398},"root-problems","root problems",[11,2401,2402,2403,2406],{},"Sometimes you'll have to run this as a root user, which WP CLI will warn you about. If you're comfortable with running this under root, WP-CLI has ",[78,2404,2405],{},"--allow-root"," as well",[11,2408,2409],{},[78,2410,2411],{},"*\u002F12 * * * * for i in $(\u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event list --fields=hook --format=ids --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite --allow-root); do \u002Fusr\u002Flocal\u002Fbin\u002Fwp cron event run $i --quiet --path=\u002Ffull\u002Fpath\u002Fto\u002Fsite --allow-root; done",[11,2413,2414,2415,2417],{},"Again, note that you must ",[78,2416,2405],{}," on both commands.",[11,2419,2420],{},"There ya go, now you can disable WP's cron runner by adding this to your wp-config.php",[11,2422,2423],{},[78,2424,2425],{},"define( 'DISABLE_WP_CRON', true );",[11,2427,2428],{},"Hope that helps!",{"title":108,"searchDepth":189,"depth":189,"links":2430},[2431,2432,2433],{"id":2297,"depth":196,"text":2298},{"id":2308,"depth":196,"text":2309},{"id":2398,"depth":196,"text":2399},"2015-10-08T00:00:00.000Z",{"headline":2278,"tags":2436},"wordpress, cron, wpcli","\u002Fblog\u002F2015-10-08-better-wordpress-cron-jobs-crontab-wpcli",{"title":2278,"description":2283},"blog\u002F2015-10-08-better-wordpress-cron-jobs-crontab-wpcli","YLBokRQjPYEkNmDw55MWRkxEeyhDhrBb_r4PITHJuEY",{"id":2442,"title":2443,"body":2444,"date":2495,"description":2496,"excerpt":1011,"extension":1012,"meta":2497,"navigation":636,"path":2499,"seo":2500,"stem":2501,"__hash__":2502},"blog\u002Fblog\u002F2015-02-28-starting-again.md","Starting Again, Without WordPress",{"type":8,"value":2445,"toc":2493},[2446,2449,2458,2473,2476,2479,2482,2485,2490],[11,2447,2448],{},"I've tried blogging for years. I wrote my first blog in ColdFusion in 2002, rewrote crude versions in PHP a few times before Matt dropped Wordpress on us at the local Blogger meetups, started extended writing habits on various web services and open source platforms from MovableType and Textpattern to LiveJornal and Tumblr.",[11,2450,2451,2452,2457],{},"This time around, I'm trying ",[15,2453,2456],{"href":2454,"rel":2455},"http:\u002F\u002Fjekyllrb.com\u002F",[19],"Jekyll",". If you know me, I live and breath WordPress daily, so a Ruby-based static site generator might seem like a pretty odd choice. But in the end, it provides dead simple content management while retaining direct control over the guts of the site.",[11,2459,2460,2461,2466,2467,2472],{},"I don't mean this as criticism of WordPress. It's an amazing example of the ",[15,2462,2465],{"href":2463,"rel":2464},"http:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPareto_principle",[19],"80\u002F20 rule",", covering the needs of the vast majority of web publishing scenarios. ",[15,2468,2471],{"href":2469,"rel":2470},"http:\u002F\u002Fwww.uptrending.com\u002F",[19],"Where I work",", we push that 80% hard, outfiting WordPress with modern interfaces and superpowers like marketing automation, user generated content, and API integrations.",[11,2474,2475],{},"WordPress's intended user base is non-developer marketing staff and it excels at providing a powerful, flexible interface that serves that audience. For this site, though, I want to be able to control my code, repurposing a page at a whim. But I still enjoy having total control over the code and hate managing databases when I don't have to.",[11,2477,2478],{},"Enter Jekyll: it's lightweight, dependent only on my local system for build and it's hosted simply on Github. I can write my posts and pages in markdown or HTML and I don't have to worry about URL management, page creation, templating, etc.",[11,2480,2481],{},"Honestly, it also keeps me from getting lost in writing huge custom features and never finishing them. The static nature of Jekyll is a major shift in perspective from the realtime, dynamic webapp world I'm used to. I think that challenge to the status quo is a big asset.",[11,2483,2484],{},"Its easy to get lost in the tool you use most. WordPress, Rails, Laravel, Node, Angular, it doesn't matter. There's usually an opinion of a \"best\" platform, but truthfully none of them fit every scenario and some are better suited to others.",[46,2486,2487],{},[11,2488,2489],{},"When all you have is a hammer, everything looks like a nail.",[11,2491,2492],{},"Just like this site, sometimes Wordpress isn't the best fit. If I don't try new options, I'd never know. So, this is my forray into production usage of a static site generator. Perhaps I'll get frustrated by the confines of runtime generation, or, maybe coloring in the lines is another way of defining the canvas. For now, I'm enjoying the flexiblity of a static site without all the fuss of manually managing site assets and layout changes. And when I do recommend WordPress, I'll be confident that it's the right tool for the job.",{"title":108,"searchDepth":189,"depth":189,"links":2494},[],"2015-02-28","An iteration of keeping a blog",{"headline":2443,"tags":2498},"wordpress, jekyll, development, featured","\u002Fblog\u002F2015-02-28-starting-again",{"title":2443,"description":2496},"blog\u002F2015-02-28-starting-again","hIc8wDlGZdzlJoj7CMO5X8VT1MgzpT9eg-0L1gvDEE8",1786108218156]