Tariffs API for Saleor.
Wire US duty calculation into a Saleor checkout using a Saleor App that listens to checkout webhooks and calls Tariffs API.
Saleor's App framework is the right place to bolt on tax-style data. A small app subscribes to CHECKOUT_CALCULATE_TAXES or a calculate_taxes synchronous webhook, calls the Tariffs API resolve endpoint, and returns the duty amount back to Saleor as an extra line item or shipping price. This page shows the shape of that app.
Built for Saleor developers.
Webhook-native
Saleor's sync webhook for tax calculation is the same hook tax providers use. We slot into it cleanly.
TypeScript or Python
Build the app with Saleor's official app-sdk in TypeScript or a small FastAPI service in Python. Either works.
No GraphQL gymnastics
Your app handles the GraphQL side toward Saleor. Toward us, it's a single REST call. No client library to learn.
US-stacking correct
Returns base duty plus Section 301, 232, IEEPA, and Chapter 99 in one call. You forward the total to Saleor.
Drop it in.
Skeleton of a Saleor App webhook handler. The full app needs install + permission setup per the Saleor docs.
1. Saleor App webhook (TypeScript, app-sdk)
typescriptimport { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"
import { saleorApp } from "../../../saleor-app"
export const calculateTaxesWebhook = new SaleorSyncWebhook({
name: "Calculate taxes (duty)",
webhookPath: "api/webhooks/calculate-taxes",
event: "CHECKOUT_CALCULATE_TAXES",
apl: saleorApp.apl,
query: "...",
})
export default calculateTaxesWebhook.createHandler(async (req, res, ctx) => {
const lines = ctx.payload.taxBase.lines
let dutyTotal = 0
for (const line of lines) {
const hts = line.sourceLine.variant?.product?.metadata?.find(m => m.key === "hts_code")?.value
const origin = line.sourceLine.variant?.product?.metadata?.find(m => m.key === "country_of_origin")?.value
if (!hts || !origin) continue
const r = await fetch(
`https://tariffsapi.com/api/v1/tariffs/resolve?hts=${hts}&origin=${origin}`,
{ headers: { Authorization: `Bearer ${process.env.TARIFFSAPI_KEY}` } }
)
const data = await r.json()
const rate = data.summary.total_resolved_ad_valorem_rate / 100
dutyTotal += Number(line.unitAmount) * line.quantity * rate
}
return res.status(200).json({
shipping_price_gross_amount: ctx.payload.taxBase.shippingPrice.amount,
shipping_price_net_amount: ctx.payload.taxBase.shippingPrice.amount,
shipping_tax_rate: 0,
lines: lines.map(l => ({
tax_rate: 0,
total_gross_amount: Number(l.unitAmount) * l.quantity,
total_net_amount: Number(l.unitAmount) * l.quantity,
})),
duty_total: dutyTotal,
})
})
2. Python alternative (FastAPI)
pythonimport os
import httpx
from fastapi import FastAPI, Request
app = FastAPI()
KEY = os.environ["TARIFFSAPI_KEY"]
@app.post("/api/webhooks/calculate-taxes")
async def calculate_taxes(req: Request):
payload = await req.json()
duty_total = 0.0
async with httpx.AsyncClient() as client:
for line in payload["taxBase"]["lines"]:
meta = {m["key"]: m["value"] for m in (line["sourceLine"]["variant"]["product"]["metadata"] or [])}
hts, origin = meta.get("hts_code"), meta.get("country_of_origin")
if not (hts and origin):
continue
r = await client.get(
f"https://tariffsapi.com/api/v1/tariffs/resolve",
params={"hts": hts, "origin": origin},
headers={"Authorization": f"Bearer {KEY}"},
)
data = r.json()
rate = data["summary"]["total_resolved_ad_valorem_rate"] / 100
duty_total += float(line["unitAmount"]) * line["quantity"] * rate
return {"duty_total": duty_total}
One endpoint. Deterministic JSON.
The resolve endpoint returns a single JSON object per HTS code and origin pair. For a multi-line cart, the bulk resolve_batch endpoint accepts up to 200 codes in one call, which keeps webhook latency low.
GET /api/v1/tariffs/resolve?hts=8541.10.00.80&origin=CN
{
"summary": {
"applicable_ad_valorem_rate": 0.0,
"resolved_additional_ad_valorem_rate": 25.0,
"total_resolved_ad_valorem_rate": 25.0
},
"base_tariff": { "percentage_component": 0.0 },
"additional_measures": [
{
"program": "section_301",
"chapter_99_code": "9903.91.05",
"resolved_rate": { "percentage_component": 0.25 }
}
]
}
Saleor + Tariffs API
Where do I store the HTS code on a Saleor product?
Product metadata. Saleor's metadata API lets you set arbitrary key-value pairs per product or variant. Store hts_code and country_of_origin as keys and read them in the webhook handler.
Is this the same as Saleor's tax integration?
Yes, it uses the same CHECKOUT_CALCULATE_TAXES sync webhook that tax providers (Avalara, Taxjar) use. The only difference is what the app returns: instead of sales tax, it returns the duty amount.
Can I run the app on the same server as Saleor?
Yes. A Saleor App is just an HTTP service that responds to webhooks. Deploy it anywhere Saleor can reach via HTTPS.
Latency concerns?
Tariffs API's resolve endpoint is fast (sub-200ms p95). For multi-line checkouts, batch via resolve_batch to keep total webhook time under a second.
- Saleor App SDK · accessed 2026-05-14
- Saleor tax calculation webhooks · accessed 2026-05-14