envelope = f""" {APPLICATION_TOKEN} {CONTEXT_IDENTIFIER} {SIGNED_SYSTEM_TOKEN} {RETURN_TOKEN_TYPE} """.strip() headers = { "Content-Type": "text/xml; charset=utf-8", "SOAPAction": "http://www.superoffice.com/superid/partnersystemuser/0.1/IPartnerSystemUserService/Authenticate", } resp = requests.post( SOAP_URL, data=envelope.encode("utf-8"), headers=headers, timeout=30 ) print(resp) # --- Useful diagnostics if server responds with HTML or error --- ct = resp.headers.get("Content-Type", "") if "xml" not in ct.lower(): print("Unexpected response (not XML). Status:", resp.status_code) print("Content-Type:", ct) print(resp.text[:1200]) raise SystemExit("Check URL, SOAPAction, or required SOAP headers/values.") # --- Parse SOAP response per WSDL --- root = ET.fromstring(resp.text) ns = { "s": "http://schemas.xmlsoap.org/soap/envelope/", "tns": "http://www.superoffice.com/superid/partnersystemuser/0.1", } # Fault? fault = root.find(".//s:Fault", ns) if fault is not None: print(resp.text) raise SystemExit("SOAP Fault returned. See XML above.") # Extract AuthenticationResponse is_ok_el = root.find(".//tns:AuthenticationResponse/tns:IsSuccessful", ns) token_el = root.find(".//tns:AuthenticationResponse/tns:Token", ns) err_el = root.find(".//tns:AuthenticationResponse/tns:ErrorMessage", ns) is_ok = is_ok_el is not None and (is_ok_el.text or "").strip().lower() == "true" token = token_el.text.strip() if token_el is not None and token_el.text else None error_msg = (err_el.text or "").strip() if err_el is not None else "" if not is_ok: print("Authenticate returned IsSuccessful = false") print("ErrorMessage:", error_msg) print(resp.text) raise SystemExit("Authentication failed.") print("Authenticate succeeded.") print("Token (truncated):", (token[:50] + "...") if token else None) # If ReturnTokenType=Jwt, you can inspect claims to find the SOTicket claim key if RETURN_TOKEN_TYPE.lower() == "jwt" and token and "." in token: def b64url_decode(seg: str) -> bytes: seg += "=" * ((4 - len(seg) % 4) % 4) return base64.urlsafe_b64decode(seg.encode("ascii")) header_b64, payload_b64, sig_b64 = token.split(".", 2) payload = json.loads(b64url_decode(payload_b64)) print("JWT payload keys:", list(payload.keys())) # Try to locate a ticket-like claim (key name may vary by env) ticket = None for k, v in payload.items(): if isinstance(v, str) and ( "ticket" in k.lower() or v.startswith(("7T:", "8A:", "8C:")) ): ticket = v break if ticket: print("Extracted system-user ticket:", ticket) # Example REST call using SOTicket + SO-AppToken headers: TENANT_BASE = ( "https://online.superoffice.com/Cust26703" # use your actual sodN host! ) rest_headers = { "Authorization": f"SOTicket {ticket}", "SO-AppToken": APPLICATION_TOKEN, # same value as ApplicationToken "Accept": "application/json", } test = requests.get( f"{TENANT_BASE}/api/v1/contact/1", headers=rest_headers, timeout=30 ) print("REST test:", test.status_code, test.text) else: print( "No obvious 'ticket' claim found in JWT. Inspect payload above and pick the correct claim manually." ) else: print( "Returned token is not a JWT (or you requested SAML). Use the token as intended by your flow." )