[{"data":1,"prerenderedAt":628},["ShallowReactive",2],{"seite-\u002Fen\u002Fratgeber\u002Fbexio-api-wordpress\u002F":3},{"id":4,"title":5,"body":6,"description":619,"extension":620,"meta":621,"navigation":622,"path":623,"robots":624,"seo":625,"stem":626,"__hash__":627},"seiten_en\u002Fratgeber\u002Fbexio-api-wordpress.md","Connect the bexio API to WordPress, a guide with OAuth, tokens and endpoints",{"type":7,"value":8,"toc":609},"minimark",[9,14,18,23,28,60,67,71,93,96,100,107,219,226,237,241,248,260,263,267,270,309,312,442,446,513,517,573,576,580,583,599,605],[10,11,13],"h1",{"id":12},"connect-the-bexio-api-to-wordpress","Connect the bexio API to WordPress",[15,16,17],"p",{},"An enquiry from the website form should arrive in bexio as a contact, an order as an invoice, a\nshop customer as an address. For that, WordPress has to talk to the bexio API. This guide walks\nfrom the app in the Developer Portal to the first contact created, the pitfalls along the way,\nand when a ready-made plugin is worth more than your own code.",[19,20],"produkt-ablauf",{":schritte":21,"titel":22},"[{\"ort\":\"Website form\",\"text\":\"A visitor sends an enquiry\"},{\"ort\":\"WordPress\",\"text\":\"Plugin or own code with a token\",\"eigen\":true},{\"ort\":\"auth.bexio.com\",\"text\":\"Token via OAuth 2, refreshed regularly\"},{\"ort\":\"api.bexio.com\",\"text\":\"Find or create the contact, add a note\"}]","How it works: the website form sends the enquiry to WordPress, WordPress gets a token from auth.bexio.com and creates contact and note through api.bexio.com",[24,25,27],"h2",{"id":26},"what-the-bexio-api-is","What the bexio API is",[15,29,30,31,35,36,39,40,43,44,47,48,51,52,59],{},"The bexio API is a REST interface using JSON. All endpoints live under ",[32,33,34],"code",{},"https:\u002F\u002Fapi.bexio.com",",\nand depending on the area the paths start with ",[32,37,38],{},"\u002F2.0\u002F",", ",[32,41,42],{},"\u002F3.0\u002F"," or newer, for example\n",[32,45,46],{},"\u002F2.0\u002Fcontact"," for contacts and ",[32,49,50],{},"\u002F3.0\u002Fusers\u002Fme"," for the signed-in user. The reference is at\n",[53,54,58],"a",{"href":55,"rel":56},"https:\u002F\u002Fdocs.bexio.com\u002F",[57],"nofollow","docs.bexio.com",". According to its own documentation, bexio offers no\nOpenAPI description, so a client is written by hand.",[15,61,62,63,66],{},"Every request needs an access token in the ",[32,64,65],{},"Authorization: Bearer …"," header. How WordPress gets\nthat token is the real work.",[24,68,70],{"id":69},"step-1-create-an-app-in-the-developer-portal","Step 1: create an app in the Developer Portal",[72,73,74,84,87,90],"ol",{},[75,76,77,78,83],"li",{},"Sign in to the ",[53,79,82],{"href":80,"rel":81},"https:\u002F\u002Fdeveloper.bexio.com\u002F",[57],"Developer Portal"," with the bexio account.",[75,85,86],{},"Read and accept the terms of use, especially section 4.4 on commercial use (see pitfalls).",[75,88,89],{},"Create a new app and enter the redirect URL bexio sends the user back to after signing in,\nfor example the plugin's settings page in the WordPress admin. Up to ten addresses are\npossible, for instance for test and production.",[75,91,92],{},"Read the Client ID and Client Secret under \"App Details\".",[15,94,95],{},"The Client Secret stays on the server, never in JavaScript in the browser.",[24,97,99],{"id":98},"step-2-sign-in-with-oauth-2","Step 2: sign in with OAuth 2",[15,101,102,103,106],{},"bexio signs in through OpenID Connect on ",[32,104,105],{},"auth.bexio.com",", using the \"Authorization Code Flow\".\nWordPress sends the user to the bexio sign-in page:",[108,109,114],"pre",{"className":110,"code":111,"language":112,"meta":113,"style":113},"language-bash shiki shiki-themes github-light github-dark","https:\u002F\u002Fauth.bexio.com\u002Frealms\u002Fbexio\u002Fprotocol\u002Fopenid-connect\u002Fauth\n  ?client_id=\u003CClient ID>\n  &redirect_uri=\u003Cregistered redirect URL>\n  &response_type=code\n  &scope=openid offline_access contact_edit note_edit\n  &state=\u003Crandom value>\n","bash","",[32,115,116,125,149,172,183,203],{"__ignoreMap":113},[117,118,121],"span",{"class":119,"line":120},"line",1,[117,122,124],{"class":123},"sScJk","https:\u002F\u002Fauth.bexio.com\u002Frealms\u002Fbexio\u002Fprotocol\u002Fopenid-connect\u002Fauth\n",[117,126,128,131,135,139,142,145],{"class":119,"line":127},2,[117,129,130],{"class":123},"  ?client_id",[117,132,134],{"class":133},"sZZnC","=",[117,136,138],{"class":137},"sVt8B","\u003CClient ",[117,140,141],{"class":133},"I",[117,143,144],{"class":137},"D",[117,146,148],{"class":147},"szBVR",">\n",[117,150,152,155,158,161,164,167,170],{"class":119,"line":151},3,[117,153,154],{"class":137},"  &redirect_uri",[117,156,157],{"class":147},"=\u003C",[117,159,160],{"class":133},"registered",[117,162,163],{"class":123}," redirect",[117,165,166],{"class":133}," UR",[117,168,169],{"class":137},"L",[117,171,148],{"class":147},[117,173,175,178,180],{"class":119,"line":174},4,[117,176,177],{"class":137},"  &response_type",[117,179,134],{"class":147},[117,181,182],{"class":133},"code\n",[117,184,186,189,191,194,197,200],{"class":119,"line":185},5,[117,187,188],{"class":137},"  &scope",[117,190,134],{"class":147},[117,192,193],{"class":133},"openid",[117,195,196],{"class":123}," offline_access",[117,198,199],{"class":133}," contact_edit",[117,201,202],{"class":133}," note_edit\n",[117,204,206,209,211,214,217],{"class":119,"line":205},6,[117,207,208],{"class":137},"  &state",[117,210,157],{"class":147},[117,212,213],{"class":133},"random",[117,215,216],{"class":123}," value",[117,218,148],{"class":137},[15,220,221,222,225],{},"The user signs in and confirms the permissions. bexio redirects back with a code, which WordPress\nexchanges, together with Client ID and Secret, at the token endpoint\n",[32,223,224],{},"\u002Frealms\u002Fbexio\u002Fprotocol\u002Fopenid-connect\u002Ftoken"," for an access token and a refresh token.",[15,227,228,229,232,233,236],{},"About scopes: a write permission includes read access, so ",[32,230,231],{},"contact_edit"," is enough for searching\ntoo. ",[32,234,235],{},"offline_access"," is needed for the refresh token. And the API always works with the\npermissions of the user who set up the connection: if that user cannot see contacts in bexio,\nneither can the app.",[24,238,240],{"id":239},"step-3-store-and-refresh-tokens","Step 3: store and refresh tokens",[15,242,243,244,247],{},"The access token expires quickly. Before it does, WordPress gets a new one with the refresh token\nand ",[32,245,246],{},"grant_type=refresh_token",", all values in the request body, not in the URL. Keep in mind:",[249,250,251,254,257],"ul",{},[75,252,253],{},"Always store the new refresh token returned by the refresh.",[75,255,256],{},"If a connection goes a year without a refresh, bexio closes the session; someone then has to\nsign in again.",[75,258,259],{},"Store tokens in WordPress options without autoload, so they are not loaded on every page view.",[15,261,262],{},"For your own scripts there are also personal access tokens (PAT). They have full access to all of\nthe company's data and are valid for 60 days. Handy for personal use, they are not suitable for a\nplugin on a customer's website.",[24,264,266],{"id":265},"step-4-create-a-contact-and-a-note","Step 4: create a contact and a note",[15,268,269],{},"A typical flow for a form enquiry needs four calls:",[72,271,272,286,292,303],{},[75,273,274,277,278,281,282,285],{},[32,275,276],{},"GET \u002F3.0\u002Fusers\u002Fme"," returns the user's ID. It is required when creating, as ",[32,279,280],{},"user_id"," and\n",[32,283,284],{},"owner_id",".",[75,287,288,291],{},[32,289,290],{},"POST \u002F2.0\u002Fcontact\u002Fsearch"," checks by email address whether the contact already exists.",[75,293,294,295,298,299,302],{},"If not: ",[32,296,297],{},"POST \u002F2.0\u002Fcontact"," creates it, ",[32,300,301],{},"contact_type_id"," 1 for companies, 2 for people.",[75,304,305,308],{},[32,306,307],{},"POST \u002F2.0\u002Fnote"," attaches the form text to the contact as a note.",[15,310,311],{},"The call to create a company looks like this:",[108,313,317],{"className":314,"code":315,"language":316,"meta":113,"style":113},"language-json shiki shiki-themes github-light github-dark","{\n  \"contact_type_id\": 1,\n  \"name_1\": \"Example Ltd\",\n  \"street_name\": \"Bahnhofstrasse\",\n  \"house_number\": \"1\",\n  \"postcode\": \"8001\",\n  \"city\": \"Zurich\",\n  \"mail\": \"info@example.ch\",\n  \"user_id\": 1,\n  \"owner_id\": 1\n}\n","json",[32,318,319,324,339,351,363,375,387,400,413,425,436],{"__ignoreMap":113},[117,320,321],{"class":119,"line":120},[117,322,323],{"class":137},"{\n",[117,325,326,330,333,336],{"class":119,"line":127},[117,327,329],{"class":328},"sj4cs","  \"contact_type_id\"",[117,331,332],{"class":137},": ",[117,334,335],{"class":328},"1",[117,337,338],{"class":137},",\n",[117,340,341,344,346,349],{"class":119,"line":151},[117,342,343],{"class":328},"  \"name_1\"",[117,345,332],{"class":137},[117,347,348],{"class":133},"\"Example Ltd\"",[117,350,338],{"class":137},[117,352,353,356,358,361],{"class":119,"line":174},[117,354,355],{"class":328},"  \"street_name\"",[117,357,332],{"class":137},[117,359,360],{"class":133},"\"Bahnhofstrasse\"",[117,362,338],{"class":137},[117,364,365,368,370,373],{"class":119,"line":185},[117,366,367],{"class":328},"  \"house_number\"",[117,369,332],{"class":137},[117,371,372],{"class":133},"\"1\"",[117,374,338],{"class":137},[117,376,377,380,382,385],{"class":119,"line":205},[117,378,379],{"class":328},"  \"postcode\"",[117,381,332],{"class":137},[117,383,384],{"class":133},"\"8001\"",[117,386,338],{"class":137},[117,388,390,393,395,398],{"class":119,"line":389},7,[117,391,392],{"class":328},"  \"city\"",[117,394,332],{"class":137},[117,396,397],{"class":133},"\"Zurich\"",[117,399,338],{"class":137},[117,401,403,406,408,411],{"class":119,"line":402},8,[117,404,405],{"class":328},"  \"mail\"",[117,407,332],{"class":137},[117,409,410],{"class":133},"\"info@example.ch\"",[117,412,338],{"class":137},[117,414,416,419,421,423],{"class":119,"line":415},9,[117,417,418],{"class":328},"  \"user_id\"",[117,420,332],{"class":137},[117,422,335],{"class":328},[117,424,338],{"class":137},[117,426,428,431,433],{"class":119,"line":427},10,[117,429,430],{"class":328},"  \"owner_id\"",[117,432,332],{"class":137},[117,434,435],{"class":328},"1\n",[117,437,439],{"class":119,"line":438},11,[117,440,441],{"class":137},"}\n",[24,443,445],{"id":444},"common-pitfalls","Common pitfalls",[249,447,448,455,461,478,488,501,507],{},[75,449,450,454],{},[451,452,453],"strong",{},"Redirect URL:"," it has to match the Developer Portal exactly, otherwise sign-in stops with an\nerror message.",[75,456,457,460],{},[451,458,459],{},"New scopes:"," a connection's permissions do not change on refresh. If the app needs more, the\nuser has to sign in again.",[75,462,463,466,467,470,471,474,475,285],{},[451,464,465],{},"Address fields:"," the ",[32,468,469],{},"address"," field is deprecated when creating. Street and house number go\ninto ",[32,472,473],{},"street_name"," and ",[32,476,477],{},"house_number",[75,479,480,483,484,487],{},[451,481,482],{},"Line breaks in notes:"," bexio shows the text of a note without line breaks. For paragraphs,\nuse ",[32,485,486],{},"\u003Cbr>"," and escape the values as HTML.",[75,489,490,493,494,474,497,500],{},[451,491,492],{},"Rate limit:"," too many requests per minute and the API answers with status 429. The headers\n",[32,495,496],{},"RateLimit-Remaining",[32,498,499],{},"RateLimit-Reset"," tell you how long to wait.",[75,502,503,506],{},[451,504,505],{},"Slow forms:"," calling bexio while the form is submitted keeps the visitor waiting and loses\nthe enquiry if bexio does not respond. Better to transfer in the background and retry on\nerrors.",[75,508,509,512],{},[451,510,511],{},"Commercial use:"," under section 4.4 of the terms, anyone running a business model of their own\non the API with at least five bexio accounts connected has to inform bexio.",[24,514,516],{"id":515},"three-approaches-compared","Three approaches compared",[518,519,520,536],"table",{},[521,522,523],"thead",{},[524,525,526,530,533],"tr",{},[527,528,529],"th",{},"Approach",[527,531,532],{},"Fits when",[527,534,535],{},"Keep in mind",[537,538,539,551,562],"tbody",{},[524,540,541,545,548],{},[542,543,544],"td",{},"Build it yourself",[542,546,547],{},"you have developers and the process is very specific",[542,549,550],{},"sign-in, token refresh, error handling and updates stay your own work for good",[524,552,553,556,559],{},[542,554,555],{},"Zapier or Make",[542,557,558],{},"other workflows already run there",[542,560,561],{},"one more service the form data passes through, mapping and duplicates by hand",[524,563,564,567,570],{},[542,565,566],{},"Ready-made plugin",[542,568,569],{},"the process follows a common pattern",[542,571,572],{},"less freedom than your own code",[15,574,575],{},"Make and Zapier offer bexio as an app of its own. The form plugin sends the enquiry there by\nwebhook, and a bexio action creates the contact.",[24,577,579],{"id":578},"ready-made-solutions","Ready-made solutions",[15,581,582],{},"For the two most common cases there are my integrations:",[249,584,585,592],{},[75,586,587,591],{},[53,588,590],{"href":589},"\u002Fen\u002Fbexio-formular-connector\u002F","bexio form connector",": WordPress plugin, enquiries from the\nwebsite form become contacts with a note in bexio, duplicates are recognised by email. Each\nsite owner connects their bexio through their own app, and the data goes straight from the\nwebsite to bexio.",[75,593,594,598],{},[53,595,597],{"href":596},"\u002Fen\u002Fbexio-hubspot\u002F","bexio ↔ HubSpot",": a won deal in HubSpot becomes a quote or invoice in\nbexio, and the payment status flows back into the deal.",[15,600,601],{},[53,602,604],{"href":603},"\u002Fen\u002Fratgeber\u002F","All guides",[606,607,608],"style",{},"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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":113,"searchDepth":127,"depth":127,"links":610},[611,612,613,614,615,616,617,618],{"id":26,"depth":127,"text":27},{"id":69,"depth":127,"text":70},{"id":98,"depth":127,"text":99},{"id":239,"depth":127,"text":240},{"id":265,"depth":127,"text":266},{"id":444,"depth":127,"text":445},{"id":515,"depth":127,"text":516},{"id":578,"depth":127,"text":579},"Connect the bexio API to WordPress: create an app in the Developer Portal, sign in via OAuth 2, refresh tokens, create contacts and notes. With the usual pitfalls and three approaches compared.","md",{},true,"\u002Fratgeber\u002Fbexio-api-wordpress",null,{"title":5,"description":619},"ratgeber\u002Fbexio-api-wordpress","sYagvqq40Bgwjr02y-cMRArLWPDgCJLWaq93bKRYJPA",1791118316437]