Step-by-step guide to set up the Zuora ↔ Vertex ↔ Kintsugi integration.
Zuora Billing tenant (sandbox or production)
Vertex O Series account (sandbox or production)
Kintsugi Platform account with Zuora connection capability
Log in to Zuora at https://rest.test.zuora.com (sandbox) or https://rest.zuora.com (production)
Navigate to Settings → Administration → Manage Users
Click on your user → OAuth Clients tab
Click Create OAuth Client
Give it a name (e.g. “Kintsugi Integration”)
Select permissions: Full Access (or minimum: API Access, Billing, Finance)
Click Create — save the Client ID and Client Secret immediately (secret is only shown once)
curl -X POST "https://rest.test.zuora.com/oauth/token" \ -d "grant_type=client_credentials" \ -d "client_id=<YOUR_CLIENT_ID>" \ -d "client_secret=<YOUR_CLIENT_SECRET>"
Expected: access_token in response.
Go to Settings → Security & Credentials
2. Select Rest API for O Series Calculation as show below, Partition needs to be selected as per your Orgs settings.
Identity Provider - VERX IDP
Save the Credentials
This will generate unique client id and client secret(Note them down)
Field | Where to Find | Example |
|---|---|---|
Username | Vertex admin portal → API credentials |
|
Password | Vertex admin portal → API credentials | (your password) |
Trusted ID | Vertex admin portal → Security → Trusted IDs | (alphanumeric string) |
SOAP URL | Vertex admin portal → Endpoints |
|
A Company configured (e.g. company code: KINTSUGI)
A valid seller/origin address (physical nexus address)
Tax rules configured for your jurisdictions
In Zuora, go to Home Page → Marketplace
Find Vertex O Series Tax Connector
Click Install
Username: Client ID from Part 2
Password: Client Secret from part 2
Security Token: Your Vertex Trusted ID
URL: https://nsconnect.vertexsmb.com/vertex-ws/services/CalculateTax90
Submit a request to https://support.zuora.com to enable the Connect Tax Engines feature on your tenant
In Zuora, go to Home Page → Purchases → Open app → Deploy
You should see below screen
In custom , Click New and fill below and click Save Login
Username: Client ID from Part 2
Password: Client Secret from part 2
Security Token: Your Vertex Trusted ID
URL: https://nsconnect.vertexsmb.com/vertex-ws/services/CalculateTax90
In Zuora , Click New and fill below and click Save Login
Select Sandbox or Production based on which type of instance you have. Check your instance url and form the url to fill. For below example the url will be
”https://test.zuora.com/apps/services/a/80”
username will be Client id , password will client secret as generated in Step 1
Click Create to deploy the app.
Note: If you face any error like
No organization associated to this tenant(Kintsugi Trial Tenant) please contact support@zuora.com
The contact support , they will give a temporary token
Go to Setup Tax Engine and Tax Date-> Setup New Tax Engine-> Connect → Enter token and error will be fixed. Try again with Part 3 and it should work.
Once the app is installed it should be available Billing Settings → Set up Tax Engine and Tax Date
Open Vertex → navigate to Company and Seller info, Fill out the addresses and Click Save.
If sellerInfo is null/empty, all Vertex tax calculations will fail with “The LocationRole being added is invalid”.
Note down Tax engine ID.
curl --location 'https://rest.test.zuora.com/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode 'client_id=<Zuora client id>' \ --data-urlencode 'client_secret=<Zuora client secret>'
From response take access_token and use access token to generate company tax id.
curl -X GET "https://rest.test.zuora.com/settings/tax-companies?taxEngineId=<TAX_ENGINE_ID>" \ -H "Authorization: Bearer <token>"
Save the id from the response — this is the taxCompanyId needed for creating tax codes.
Go to Request Template and paste below SOAP
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<VertexEnvelope xmlns="urn:vertexinc:o-series:tps:7:0">
<Login>
<UserName>{{seller["username"]}}</UserName>
<Password>{{seller["password"]}}</Password>
<TrustedId>{{seller["security_token"]}}</TrustedId>
</Login>
<{% if document["preview_mode"] %}Quotation{% else %}Invoice{% endif %}Request documentDate="{{today}}" documentNumber="{{document["invoice_date"]}}" returnAssistedParametersIndicator="true" returnGeneratedLineItemsIndicator="true" transactionId="{{document["id"]}}" transactionType="SALE">
<Seller>
<Company>{{seller["company_code"]}}</Company>
<PhysicalOrigin>
<StreetAddress1>{{seller["address1"]}}</StreetAddress1>
<StreetAddress2>{{seller["address2"]}}</StreetAddress2>
<City>{{seller["city"]}}</City>
<PostalCode>{{seller["zipCode"]}}</PostalCode>
<Country>{{seller["country"]}}</Country>
</PhysicalOrigin>
</Seller>
<Currency isoCurrencyCodeAlpha="{{document["currency"]}}"/>
<OriginalCurrency isoCurrencyCodeAlpha="{{document["currency"]}}"/>
{% for document_item in document_items %}
<LineItem isMulticomponent="false" lineItemId="{{document_item["id"]}}" taxDate="{{document_item["taxDate"]}}" taxIncludedIndicator="false">
<Product productClass="{{document_item["subscriptionRatePlanChargeId"]}}">{{document_item["taxCode"]}}</Product>
{% if document_item["taxExemptStatus"] 1 or document_item["taxExemptStatus"] "Yes" %}<Customer exemptionReasonCode="RESALE" isTaxExempt="true">{% else %}<Customer>{% endif %}
<CustomerCode>{{document_item["customer"]["accountNumber"]}}</CustomerCode>
<Destination>
<StreetAddress1>{{document_item["customer"]["address1"]}}</StreetAddress1>
<StreetAddress2>{{document_item["customer"]["address2"]}}</StreetAddress2>
<City>{{document_item["customer"]["city"]}}</City>
<MainDivision>{{document_item["customer"]["state"]}}</MainDivision>
<PostalCode>{{document_item["customer"]["zipCode"]}}</PostalCode>
<Country>{{document_item["customer"]["country"]}}</Country>
</Destination>
{% if document_item["taxExemptStatus"] 1 or document_item["taxExemptStatus"] "Yes" %}<ExemptionCertificate exemptionCertificateNumber="{{document_item["taxExemptCertificateID"]}}"/>{% endif %}
</Customer>
{% if document["event_type"] == 'taxOverride' %}
<TaxOverride overrideType="NONTAXABLE"/>
{% endif %}
<Discount>
<DiscountAmount>{{document_item["discountAmount"]}}</DiscountAmount>
</Discount>
<Quantity>{{document_item["quantity"]}}</Quantity>
<UnitPrice>{{document_item["unitPrice"]}}</UnitPrice>
<ExtendedPrice>{{document_item["totalAmount"]}}</ExtendedPrice>
<FlexibleFields>
<FlexibleCodeField fieldId="1"/>
<FlexibleCodeField fieldId="2"/>
<FlexibleCodeField fieldId="3"/>
<FlexibleCodeField fieldId="4"/>
<FlexibleCodeField fieldId="5"/>
<FlexibleCodeField fieldId="6"/>
<FlexibleCodeField fieldId="7"/>
<FlexibleCodeField fieldId="8"/>
<FlexibleCodeField fieldId="9"/>
<FlexibleCodeField fieldId="10"/>
<FlexibleCodeField fieldId="11"/>
<FlexibleCodeField fieldId="12"/>
<FlexibleCodeField fieldId="13"/>
<FlexibleCodeField fieldId="14"/>
<FlexibleCodeField fieldId="15"/>
<FlexibleCodeField fieldId="16"/>
<FlexibleCodeField fieldId="17"/>
<FlexibleCodeField fieldId="18"/>
<FlexibleCodeField fieldId="19"/>
<FlexibleCodeField fieldId="20"/>
<FlexibleCodeField fieldId="21"/>
<FlexibleCodeField fieldId="22"/>
<FlexibleCodeField fieldId="23"/>
<FlexibleCodeField fieldId="24"/>
<FlexibleCodeField fieldId="25"/>
<FlexibleNumericField fieldId="1"/>
<FlexibleNumericField fieldId="2"/>
<FlexibleNumericField fieldId="3"/>
<FlexibleNumericField fieldId="4"/>
<FlexibleNumericField fieldId="5"/>
<FlexibleNumericField fieldId="6"/>
<FlexibleNumericField fieldId="7"/>
<FlexibleNumericField fieldId="8"/>
<FlexibleNumericField fieldId="9"/>
<FlexibleNumericField fieldId="10"/>
<FlexibleDateField fieldId="1"/>
<FlexibleDateField fieldId="2"/>
<FlexibleDateField fieldId="3"/>
<FlexibleDateField fieldId="4"/>
<FlexibleDateField fieldId="5"/>
</FlexibleFields>
</LineItem>
{% endfor %}
</{% if document["preview_mode"] %}Quotation{% else %}Invoice{% endif %}Request>
</VertexEnvelope>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>Object | Fields | Example |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Go to Data Sources → Search Zuora → Connect
Client ID: From Part 1
Client Secret: From Part 1
Base URL: https://rest.test.zuora.com (sandbox) or https://rest.zuora.com (production)
Vertex Tax Engine ID: The id from Zuora + Vertex Connect Tax Engine — Installation Guide | Verify Tax Engine
Vertex Tax Company ID: The id from Zuora + Vertex Connect Tax Engine — Installation Guide | Get Tax Company ID
Field | Where Stored | Purpose |
|---|---|---|
Client ID | Connection config (encrypted) | OAuth authentication |
Client Secret | Connection config (encrypted) | OAuth authentication |
Base URL | Connection config | API endpoint |
Tax Engine ID |
| Used in |
Tax Company ID |
| Used in |
Error | Cause | Fix |
|---|---|---|
“The LocationRole being added is invalid” |
| Update sellerInfo with valid seller address |
“TaxCode on invoice items must be predefined and activated” | Tax code doesn't exist or is inactive | Create/activate the tax code first |
“Field 'taxCompanyId' is required” | Missing | Include |
“Cannot find entity by key” | Wrong | Use the |
Tax calculated but | Account address invalid for Vertex | Use 2-letter state abbreviation (CA, TX, NY) |
Exemption not working with Vertex | SOAP template doesn't pass exemption data | Customize template or use bill run (not standalone invoice) |
“Please deactivate the Tax Code before deleting” | Tax code is active | Deactivate first, then delete |
“Please remove the Tax Code from all charges” | Tax code still assigned to charges | Remove from all charges first, then deactivate + delete |
Item | How to Get |
|---|---|
Tax Engine ID |
|
Tax Company ID |
|
Product ID | ZOQL: |
Rate Plan ID | ZOQL: |
Charge ID | ZOQL: |
Account ID | ZOQL: |
Creates tax code on Vertex engine → POST /settings/tax-codes/connector
Assigns tax code to all charges under that product → PUT /v1/object/product-rate-plan-charge/<id>