#eczo_api
1 messages · Page 1 of 1 (latest)
đź‘‹ Welcome to your new thread!
⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
đź”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1507024211317882931
📝 Have more to share? Add more details, code, screenshots, videos, etc. below.
Adding more context and code samples for the question above.
What worked before (atomic, one call)
We computed taxes externally and passed them as regular invoice items via add_invoice_items directly in subscriptions.create. Stripe finalized and charged the first invoice as part of the request — no extra orchestration on our side.
stripe.Subscription.create(
customer=customer_id,
items=[
{"price": saas_price_id},
{"price": voice_price_id},
{"price": saas_metered_price_id},
{"price": voice_metered_price_id},
],
add_invoice_items=[
{
"price_data": {
"product": federal_tax_product_id,
"currency": "usd",
"unit_amount": federal_tax_cents,
"tax_behavior": "exclusive",
},
"quantity": 1,
},
{
"price_data": {
"product": state_tax_product_id,
"currency": "usd",
"unit_amount": state_tax_cents,
"tax_behavior": "exclusive",
},
"quantity": 1,
},
# ... local tax, admin fee, etc.
],
payment_behavior="error_if_incomplete",
expand=["latest_invoice"],
)
One API call. Stripe creates the subscription, finalizes the first invoice with the tax line items already included, auto-charges, and handles retries/dunning if anything fails. We don’t touch the invoice lifecycle.
What we want to move to
The newer tax_amounts[] structure: taxes attached per line item with tax_rate_data.jurisdiction_level and tax_type, instead of separate tax line items.
Example resulting invoice shape:
{
"lines": [{
"id": "il_XXX",
"amount": 28500,
"description": "SaaS plan",
"taxes": [
{
"amount": 1140,
"taxable_amount": 28500,
"tax_rate_data": {
"jurisdiction_level": "federal",
"tax_type": "..."
}
},
{
"amount": 1069,
"taxable_amount": 28500,
"tax_rate_data": {
"jurisdiction_level": "state"
}
},
{
"amount": 107,
"taxable_amount": 28500,
"tax_rate_data": {
"jurisdiction_level": "city"
}
}
]
}],
"total_taxes": []
}
This format can’t be passed via add_invoice_items. The only documented way we found to set tax_amounts[] is POST /v1/invoices/:id/lines/:line_id on existing draft invoice lines.
Why the simple path doesn’t work
subscriptions.create with collection_method=charge_automatically finalizes the first invoice as part of the request. By the time the API call returns, the invoice is already finalized and invoice line updates are rejected.
Different payment_behavior values (default_incomplete, error_if_incomplete, allow_incomplete) do not change this behavior.
Current workaround (SubscriptionSchedule + manual lifecycle)
The only working approach we found is to use SubscriptionSchedule to get a draft invoice, then apply tax_amounts[], disable auto-finalization, and finalize/pay the invoice ourselves.
This works, but it changes the flow from one atomic Stripe call into a custom multi-step lifecycle. We now need to handle partial failures, idempotency, reconciliation, and recovery jobs. It also means we lose some of Stripe’s automatic invoice finalization and retry/dunning safety because auto_advance=false.
Questions:
- Is there a way to keep the first subscription invoice in draft state long enough to apply tax_amounts[], while still letting Stripe finalize and charge it automatically afterward?
- Is there any webhook or extension point where Stripe can request external tax amounts before invoice finalization?
- If not, is SubscriptionSchedule + auto_advance=false + manual finalize/pay the recommended approach for external tax engines using per-line tax_amounts[]?
The same issue also applies to renewal and cancellation invoices, since they also auto-finalize shortly after creation.
Hello
I'm not aware of any other workarounds other than what you've tried already.
When you pass manual tax amounts, Stripe creates tax-rates for them automatically - https://docs.stripe.com/invoicing/taxes/manual-tax-amounts#stripe-creates-tax-rates
One option would be for you to calculate the external tax details, create a tax rate and pass it in with the specific price ID when you create the Subscription - https://docs.stripe.com/api/subscriptions/create?api-version=2026-04-22.preview&rds=1#create_subscription-items-tax_rates
If that's not something you prefer to do, you could pass trial_end: <now + 5 seconds> on subscriptions.create. Stripe creates a $0 trial invoice immediately, then when the trial ends the real invoice is created with the standard 1-hour draft window. Same webhook pattern as above. Much simpler than SubscriptionSchedule.
I have not tried that^ but could work
Thanks for the pointer. We'll look into the TaxRate + items[].tax_rates approach and see how it fits our use case. Appreciate the quick and pointed response.