Integrations
Twinfield SOAP API: 23 gotchas die btw stilletjes laten vallen
Een ranglijst van 23 Twinfield SOAP-gotchas uit een invoice-chase rollout in Zwolle, beginnend bij de gotchas die 200 OK teruggeven en je btw-regel stilletjes laten verdwijnen.

Het is 23:40 op een dinsdag in Zwolle. De nachtdienst-boekhouder bij een accountantskantoor van 41 mensen controleert nog een keer de uitgaande facturen van gisteren. Een factuur voor een Britse klant valt op: totaal €4.200, btw-regel ontbreekt, opmerking over verlegde btw ontbreekt ook. De Twinfield envelope die onze agent die middag terugkreeg meldde <result>1</result>. Onze log meldde OK.
Die factuur is juridisch fout. De klant heeft 'm betaald. De btw-aangifte klopt straks niet als niemand het opmerkt. Wij merkten het op. Daarna vonden we tweeëntwintig soortgelijke gevallen.
We draaien sinds maart een invoice-chase agent bovenop Twinfield voor dat kantoor. Twinfield is het system of record voor het merendeel van de Nederlandse mkb-boekhouding; als je er ooit tegenaan hebt geautomatiseerd ken je de vorm van de pijn. Zo niet, dan is dit wat we hadden willen krijgen op dag één: drieëntwintig gotchas, gerangschikt naar hoe stilletjes ze je beschadigen.
De 200 OK is de eindbaas
De SOAP API van Twinfield geeft vrolijk <result>1</result> terug op een envelope die technisch geldig is, intern goed gestructureerd en semantisch verkeerd. Er is geen apart waarschuwingenkanaal. Er is de result code en er is de messages-array, die soms gevuld is met informele regels die je kunt negeren en soms met die ene regel die betekent we hebben je btw stilletjes laten vallen.
Het principe is algemeen: zelfverzekerd foute antwoorden zijn riskanter dan zichtbaar kapotte. Een duidelijke 500 routeert zichzelf naar een queue. Een verkeerd geboekte factuur routeert zichzelf naar een belastinginspecteur.
Tier 1: de acht die btw stilletjes laten vallen
Deze geven 200 OK terug. Ze zien eruit als wins. Dat zijn ze niet.
1. Leeg of ontbrekend <country> op de customer dimension. De btw-engine van Twinfield leest het land van de klant uit de dimension, niet uit de factuur. Als het veld leeg is gaat de engine ervan uit dat geen bepaling mogelijk is, en laat de btw-regel volledig weg in plaats van te falen. Maak country een verplicht veld in je customer-sync, geen nice-to-have.
2. Header <vatcode> wordt overschreven door regel-<vatcode>. Veel voorbeelden online zetten de btw-code op factuurniveau. Twinfield accepteert dat. Maar 't wordt ook overschreven door wat er op elk <line>-element staat, met als default de opgeslagen code van het regeltemplate als je er geen hebt ingesteld. Zet 'm op de regel. Altijd.
3. Reverse-charge codes lijken op de gewone EU-code. Een B2B EU-klant heeft VL of VH nodig (afhankelijk van het rekeningschema van het office), niet VN. Twinfield valideert niet of het btw-nummer van de klant daadwerkelijk bestaat in VIES. Het boekt gewoon wat jij erin stopt. De factuur gaat zonder btw de deur uit en zonder de wettelijk verplichte BTW verlegd-opmerking, want die juridische tekst komt uit het template, getriggerd door de btw-code die je vergat te zetten.
4. De opgeslagen default btw-code op de klant overschrijft de payload. Als je een default <vatcode> zet op de customer dimension en je overschrijft 'm niet expliciet op de regel, wint de dimension. Dat is het omgekeerde van wat de meeste ERP's doen. Het bijt het hardst wanneer salesmensen klantgegevens handmatig bijwerken tussen agentruns door.
5. Gecacht EU-btw-nummer bij een inmiddels niet-EU-klant. Post-Brexit UK-klanten van wie het <vatnumber>-veld nog een GB-code bevat die door een oude office-configuratie als EU wordt herkend, krijgen EU-behandeling. De fix is het veld leegmaken, niet bijwerken.
6. <perfdate> belandt in een gesloten btw-periode. Twinfield verschuift de btw-regel stilletjes naar de eerstvolgende open periode zonder je iets te zeggen. Je factuurdocument en je btw-aangifte spreken nu tegen, en niemand komt erachter tot het kwartaal sluit.
7. Currency mismatch zet het btw-bedrag stilletjes op nul. Stuur <currency>USD</currency> naar een office met EUR als basis, en de amount-based btw-modus van Twinfield berekent btw tegen nul. De percentage-based modus handelt het wel goed af. De meeste oudere offices staan nog op amount-based.
8. Sub-administratie-dagboek zonder btw-mapping. Als je factuur boekt op een dagboek dat niet aan een btw-systeem hangt, boekt Twinfield de factuur en genereert simpelweg geen btw-regel. Geen error. Ook de dagboek-selector in hun UI markeert dit niet.
Elk van deze geeft <result>1</result> terug. Elk van deze komt door een geautomatiseerde end-to-end test die op success controleert. Je moet de inhoud van de envelope valideren, niet de result code.
Tier 2: de zeven die luid genoeg falen om in een queue te belanden
Deze geven je tenminste iets om op te alerten.
9. Session-cluster mismatch. Het auth-response van Twinfield bevat een <cluster>-URL. Die URL moet je gebruiken voor de volgende calls, niet de URL waar je tegen inlogde. Gebruik de verkeerde en je krijgt Invalid session ID bij de tweede call. De login-host is login.twinfield.com; cluster-hosts zien eruit als accounting1.twinfield.com of accounting4.twinfield.com. Hardcode ze niet, lees ze per session uit.
10. Verkeerde office-code boekt op een ander bedrijf. Het <office>-element in de session selecteert in welke administratie je schrijft. Verkeerd ingesteld en je factuur boekt op de boeken van een echte, andere klant. Twinfield checkt niet of de office bij de klant past. Hardcode de office in environment-config, nooit in een payload die een model kan hallucineren.
11. Session timeout geeft 200 terug, geen 401. Sessions sterven na ongeveer twintig minuten inactiviteit. De volgende call geeft een 200 OK envelope terug met Not logged in in de messages-array. Alles dat dit afhandelt met if response.status_code == 200: success laat elke operatie na de timeout stilletjes vallen.
12. Dimension type is DEB voor klanten, niet CRD. CRD staat voor crediteuren (je leveranciers). Verwissel ze en je maakt een factuur aan tegen je eigen crediteurenadministratie. Deze geeft wel een error, maar de boodschap is dimension not found, waardoor de meeste engineers op zoek gaan naar een ontbrekend record in plaats van een typo.
13. Dubbel factuurnummer in dezelfde periode. Twinfield wijst af met transaction number already exists. Als je nummers regenereert vanuit je eigen DB, vang dan het geval af waarin de counter van Twinfield is doorgelopen terwijl je agent sliep.
14. Datumformaat moet YYYYMMDD zijn, zonder scheidingstekens. 2026-06-11 levert je een parse-error op die er niet gerelateerd uitziet. De error verwijst naar de veldnaam, niet naar het formaatprobleem.
15. Bijlagen hebben <attachment> nodig, geen <file>. De element-naam verschilt per proces. Base64-encoded PDF-body, geen chunking nodig tot ongeveer 8 MB. De verkeerde tag geeft een bruikbare error; verkeerde base64 geeft een 200 terug met een corrupte blob aan de factuur.
Tier 3: de acht die simpelweg je dag opvreten
16. read.transactions pagineert op 100 rijen zonder waarschuwing. Loop op <offset> tot je een lege pagina krijgt.
17. Nederlandse decimale komma versus punt: Twinfield accepteert beide en rondt ze vervolgens anders af in edge cases met currency-conversie. Stuur altijd punten, ongeacht de locale van het office.
18. <memo> wordt zonder mededeling op zestig tekens afgekapt. Alles wat langer is wordt stilletjes ingekort.
19. <invoicedate> na periodesluiting is een soft warning gedurende de maand en een hard error na de 10e van de volgende maand.
20. WSDL-URL's in oude documentatie wijzen naar accounting.twinfield.com. De huidige endpoints zijn login.twinfield.com voor OAuth en <cluster>.twinfield.com voor SOAP. De eigen pagina's van Wolters Kluwer linken op plekken nog naar dode URL's.
21. OAuth refresh tokens verlopen na negentig dagen niet-gebruik. Als je agent dagelijks draait gaat dat prima. Test je elke twee maanden, plan dan een handmatige re-authorisatiestap in.
22. Rate limiting is per session, niet per IP. Een op hol geslagen agent sluit de rest van je infrastructuur niet buiten, maar zichzelf wel voor de eerstvolgende minuten. Back off exponentieel bij 429.
23. De XSD voor <sales> is niet dezelfde als die voor <salesinvoice>. Ze lijken op elkaar. Ze verschillen in drie veldnamen. Als je een snippet hebt gekopieerd uit een Stack Overflow-antwoord van vóór 2019, heb je de oude.
Het minimaal werkbare vangnet
Na de 23e zijn we gestopt met ze één voor één fixen en hebben we een guard gebouwd. Die draait na elke post, vóór onze agent OK logt. Skelet:
def validate_posted_invoice(envelope_xml, expected):
soup = parse(envelope_xml)
if soup.find('result').text != '1':
raise PostFailed(soup.find('messages').text)
# The eight silent VAT killers
vat_line = soup.find('line', {'type': 'vat'})
if expected.vat_due and vat_line is None:
raise SilentVATLoss('200 OK but no VAT line generated')
if expected.reverse_charge:
remarks = (soup.find('remarks') or {}).get('text', '').lower()
if 'verlegd' not in remarks:
raise SilentVATLoss('reverse-charge remark missing')
if expected.currency != soup.find('currency').text:
raise CurrencyMismatch()
posted_period = soup.find('period').text
if posted_period != expected.period:
raise PeriodDrift(f'expected {expected.period}, got {posted_period}')
return True
De guard voegt ongeveer 40 ms per factuur toe en vangt elke Tier 1-gotcha die we kennen. We lezen het geboekte document opnieuw uit in plaats van de response-envelope te vertrouwen. Vertrouw het grootboek, niet de API call.
Als je enige test voor een Twinfield-post de result code is, heb je geen test. Lees het document terug, vergelijk 'm met wat je wilde sturen, en alerteer op het verschil.
Wat je morgen kunt doen
Open je Twinfield-integratie, vind elke plek waar je result == 1 checkt en als success behandelt, en voeg één extra call toe: lees het document dat je net hebt geboekt, parse de btw-regel, vergelijk 'm met wat je agent bedoelde. Dat is een verandering van één middag werk en het vangt alle acht Tier 1-gotchas. De juridische blootstelling van één foutief geboekte btw-factuur is groter dan een dag engineering.
Toen we de invoice-chase AI-agent bouwden voor dat Zwolse accountantskantoor, was de eerste gotcha die we raakten nummer één: lege country codes bij klanten die sinds 2014 in het systeem stonden. Uiteindelijk hebben we de data gefixt, niet de agent, omdat de agent gelijk had en het grootboek niet. Dat is ongeveer de vorm van elke integratie die we opleveren: de wrapper is klein, het meeste werk zit in data-hygiëne.
Kern
Vertrouw bij Twinfield SOAP het geboekte document, niet de response code. Een 200 OK envelope geeft regelmatig success terug terwijl 'ie de btw-regel stilletjes laat vallen.
FAQ
Waarom geeft Twinfield 200 OK terug bij een mislukte btw-berekening?
De SOAP result code geeft aan of de envelope is verwerkt, niet of de business logic heeft opgeleverd wat je verwachtte. Je moet het geboekte document terug uitlezen om te checken dat de btw-regel en opmerkingen er staan.
Welke Twinfield-gotcha richt de meeste schade aan in productie?
Lege country codes op klanten. De btw-engine laat de btw-regel stilletjes weg in plaats van te falen, dus facturen gaan juridisch fout de deur uit zonder alert. Maak country verplicht op elke customer dimension die je synct.
Hoe houd je een Twinfield-session veilig in leven?
Roep volgende operaties altijd aan tegen de cluster-URL uit het auth-response, nooit de login-URL. Behandel een 200 OK envelope met 'Not logged in' als een her-auth-signaal, niet als success.
Is de Twinfield REST API een uitweg uit deze gotchas?
Deels. Auth en paginering zijn fijner, maar de btw-engine en het silent-drop gedrag zijn hetzelfde onder beide transports. Het vangnet blijft gelden: lees terug wat je hebt geboekt voordat je success logt.