Direct naar de inhoud
Van Oosten Advies B.V.

Laatst bijgewerkt: 23 september 2026

CustomerGate 365: naslag webservices

Voor ontwikkelaars die een website op CustomerGate 365 bouwen. Deze pagina beschrijft het aanmelden, de vorm van de endpoints, elke service en procedure, de JSON-sleutels en vaste waarden, de webhooks die Business Central naar u toe stuurt, en de beveiligingsregels erachter. Ze beschrijft appversie 1.0.42.0, de versie op AppSource. Sleutels en waarden met Sinds 1.0.44 komen met de volgende versie; een website die op 1.0.42.0 is gebouwd, blijft zonder die toevoegingen werken. Namen zijn hoofdlettergevoelig, en de AL-broncode van de app is doorslaggevend.

De services

CustomerGate 365 publiceert elf webservices: acht eigen en drie van iFacto. De app publiceert ze zelf bij de installatie en opnieuw bij elke update: een ontbrekende service wordt aangemaakt, en een service die onder die naam al bestaat, blijft altijd ongewijzigd, ook als iemand de publicatie met de hand heeft ingetrokken of de service heeft aangepast. De drie services van iFacto bestaan alleen waar Steel 365 is geïnstalleerd.

ServiceSoortWaarvoorAangeroepen door
voaItemspagina, alleen lezenArtikelen voor de catalogussynchronisatietoepassing
voaVariantspagina, alleen lezenArtikelvarianten (afmetingen)synchronisatietoepassing
voaCategoriespagina, alleen lezenArtikelcategorieënsynchronisatietoepassing
voaRemovalspagina, alleen lezenWat uit de catalogus verdwenen is en dus van de website af moetsynchronisatietoepassing
voaCatalogMediacodeunit, unbound actionDe bytes van één artikel- of variantfotosynchronisatietoepassing
voaProfilespagina (iFacto DXSteel)Staalprofielensynchronisatietoepassing
voaGradespagina (iFacto DXSteel)Staalkwaliteitensynchronisatietoepassing
voaItemTypespagina (iFacto DXSteel)Staalartikelsoortensynchronisatietoepassing
voaRequestscodeunit, unbound actionsEen offerte- of accountaanvraag ontvangen en er een bestand aan toevoegenwebsitetoepassing
voaPortalcodeunit, unbound actionsAanmelden en alles wat een aangemelde klant ziet of doetwebsitetoepassing
voaPingcodeunit, unbound actionWat voor installatie dit is: versie, omgeving, modules, documentbron. Zonder sessie en zonder persoonsgegevenswebsitetoepassing

De websitetoepassing mag voaRequests, voaPortal en voaPing aanroepen, de synchronisatietoepassing de cataloguspagina's, voaRemovals en voaCatalogMedia. Geen van beide kan de services van de andere gebruiken: de websitetoepassing heeft geen uitvoerrecht op de catalogus, en de synchronisatietoepassing heeft er geen op het portaal of de aanvragen. Welke toepassing welke is, staat in de volgende paragraaf.

Aanmelden

De website communiceert met Business Central als twee Microsoft Entra-toepassingen. Hun client-id's vult u in op CustomerGate-instellingen; de actie Machtigingen herstellen registreert ze in Business Central en wijst hun machtigingensets toe. Voor die actie is SUPER nodig.

Veld in de instellingenWaarvoorMag aanroepen
Client-id websiteOfferte- en accountaanvragen, het klantenportaal, de pingvoaRequests, voaPortal, voaPing
Client-id synchronisatieDe synchronisatie van de catalogus. Laat leeg als de catalogus niet wordt gesynchroniseerd.voaItems, voaVariants, voaCategories, voaRemovals, voaCatalogMedia, voaProfiles, voaGrades, voaItemTypes

Geen van beide toepassingen kan de services van de andere gebruiken: de websitetoepassing heeft geen uitvoerrecht op de cataloguspagina's, en de synchronisatietoepassing heeft er geen op voaPortal, voaRequests of voaPing.

Machtigingensets

SetToegewezen aanWat de set toestaat
VOA WEB APIwebsite, alle bedrijvenvoaRequests uitvoeren. Alleen indirect invoegen in de aanvraagtabellen, geen leesrecht.
VOA WEB PORTALwebsite, alle bedrijvenvoaPortal en de codeunits die hij aanroept uitvoeren. Alleen indirecte tabelrechten.
VOA WEB CONTRACTwebsite, alle bedrijvenvoaPing en de codeunits die hij aanroept uitvoeren (sinds 1.0.44 ook de bijlageregels die hij onder attachments meldt), en verder niets. Een eigen set, omdat de twee sets hierboven elk precies hun eigen aanroepketen afdekken en voaPing in geen van beide zit.
VOA WEB BASELINEwebsite, alle bedrijvenHet minimum dat een webservicesessie nodig heeft, in plaats van D365 BASIC, dat klant- en verkoopgegevens zou prijsgeven.
VOA CG EXECUTEwebsite, tenant-setUitvoerrechten op de objecten die het mail-, schrijf- en pdf-pad bereiken.
VOA CG MAILwebsite, tenant-setWat de e-mailconnectors nodig hebben om mail te versturen, zoals de aanmeldcode.
VOA CG S365 ARCHIVEwebsite, alleen met DXSteel Doc ArchiveDe archieftabellen lezen, met een beveiligingsfilter op de toegestane archiefrubrieken.
VOA CG DOCUMENTSwebsite, alleen zonder dat archief en met Portaaldocumenten uit rapporten aanLezen wat de standaardverkooprapporten lezen, en die rapporten uitvoeren, zodat Document pdf's kan afdrukken.
CDC BASICwebsite, alleen met Continia Document CaptureContinia haakt in op de triggers van de tabellen waarin de website schrijft.
VOA WEB SYNCsynchronisatie, per bedrijfDe vier eigen cataloguspagina's met hun brontabellen en de fotoservice, plus leesrecht op artikelposten voor de restvoorraad en op de mediatabel voor de foto's.
VOA CG S365 SYNCsynchronisatie, alleen met Steel 365De drie DXSteel-pagina's en de tabellen erachter.

Een token ophalen

Beide toepassingen gebruiken de OAuth 2.0-clientreferentiestroom:

POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token

grant_type=client_credentials
client_id={client id}
client_secret={secret}
scope=https://api.businesscentral.dynamics.com/.default

Stuur het toegangstoken bij elke aanroep mee als Authorization: Bearer {token}.

Endpoints en afspraken

Alle services zijn standaard OData v4-endpoints van Business Central onder https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/ODataV4.

Paginaservices

De cataloguspagina's leest u met GET. De referentiewebsite geeft het bedrijf aan met zijn naam:

GET .../ODataV4/Company('{company name}')/voaItems?$select=No,Description&$filter=...

In een bedrijfsnaam verdubbelt u een apostrof (O''Brien), zoals OData voorschrijft. Grote resultaten komen in pagina's: volg @odata.nextLink tot die ontbreekt. De records staan in value.

Codeunitservices

voaRequests, voaPortal, voaPing en voaCatalogMedia bieden elke openbare procedure aan als unbound action met de naam {service}_{Procedure}. Roep die aan met POST en geef de parameters op naam mee in een JSON-body. De referentiewebsite geeft het bedrijf aan met zijn GUID, de id van het bedrijf in de standaard companies-API:

POST https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/ODataV4/voaPortal_ReadSession?company={company id}
Authorization: Bearer {access token}
Content-Type: application/json
Accept: application/json
Accept-Language: en-US

{"token": "<session token>"}

De retourwaarde staat in de OData-envelop onder value:

{"@odata.context": "…", "value": "{\"email\":\"buyer@example.com\",\"customerNo\":\"C00010\", …}"}

Afspraken

  • Namen zijn hoofdlettergevoelig en de koppeling gaat op naam. voaPortal_readSession bestaat niet, en een parameter die emailAddress heet in plaats van email, levert een 400 op zonder verdere uitleg.
  • Elke parameter is een tekenreeks: alle procedureparameters zijn AL-Text. Ook een getal als pictureIndex stuurt u als "1".
  • Een JSON-resultaat komt terug als tekenreeks binnen value, omdat een unbound action geen genest object kan teruggeven. Ontleed value een tweede keer. Een booleaanse uitkomst is een JSON-true of false in value.
  • Een lege tekenreeks is een antwoord. Bij de meeste procedures van voaPortal betekent "": geen geldige sessie, of hier is niets voor u.
  • Optionele sleutels worden weggelaten, niet als null verstuurd. Behandel een ontbrekende sleutel als null.
  • Datums zijn YYYY-MM-DD, tijdstempels zijn ISO 8601 in UTC (2026-09-19T14:30:00.000Z). Een lege datum of tijdstempel is "".
  • Getallen en booleans zijn JSON-getallen en JSON-booleans.
  • Accept-Language bepaalt de taal van de Business Central-sessie. Dat heeft invloed op de foutmeldingen en de optiebijschriften in de catalogus, nooit op de vaste waarden verderop. De referentiewebsite stuurt nl-NL.

Statuscodes en opnieuw proberen

StatusWat het hier betekent
200De procedure is uitgevoerd. De uitkomst staat in value.
400Er trad een AL-fout op, bijvoorbeeld bij een geweigerde aanvraag (de melding staat in de body, met een merkteken [VOA-…] ervoor), of de parameternamen kloppen niet.
401 / 403Token of machtigingensets kloppen niet. De body noemt het object dat ontbreekt.
404De service is niet onder die naam gepubliceerd, of het bedrijf klopt niet.
409Gelijktijdige aanroepen met hetzelfde sessietoken probeerden dezelfde sessierij bij te werken.
429Business Central remt de aanroepen af. Er is niets verwerkt; houd u aan Retry-After.
5xxServerfout. Bij een aanroep die iets wijzigt, weet u niet of hij is uitgevoerd.

Na een 429 kunt u altijd veilig opnieuw proberen. Na een netwerkfout, een time-out, een 409 of een 5xx probeert u alleen de lezende procedures opnieuw: ReadSession, MyDetails, MyDeliveryAddresses, MyRequests, RequestLines, MyQuotes, MyOrders, MyShipments, DocumentLines, MyDocuments, Document, MyAttachments, Attachment, MyNotificationSettings, Ping en de cataloguspagina's.

Probeer RequestCode, SignIn, SignOut, LinkRequest, AcceptQuote, AcceptQuoteWithDetails, RejectQuote, SetNotificationSettings en InviteColleague niet op eigen initiatief opnieuw. SwitchCustomer is veilig met dezelfde customerNo: wisselen naar de klant waarop de sessie al staat, verandert niets en geeft switched terug. Bij CreateQuoteRequest en CreateAccountRequest is opnieuw proberen alleen veilig met dezelfde requestId. AddAttachment is altijd veilig met dezelfde bytes: dezelfde inhoud bij dezelfde aanvraag blijft één bijlage.

Het beveiligingsmodel

Er zijn twee lagen. De Entra-toepassing bewijst dat de aanroeper de website is; ze identificeert nooit een klant. Het sessietoken van het portaal identificeert de aangemelde persoon, en Business Central leidt daar de klant uit af. Het klantnummer gaat niet van de website naar Business Central: alleen SwitchCustomer accepteert er een, en die wisselt alleen tussen klanten die aan het aangemelde adres gekoppeld zijn. Geen enkele andere procedure accepteert een klantnummer, een e-mailfilter of een andere parameter die bepaalt wiens gegevens er terugkomen.

Portaalgebruikers

  • Iemand mag het portaal gebruiken zodra de binnendienst zijn accountaanvraag heeft goedgekeurd. Daarmee ontstaat een portaalgebruiker: een e-mailadres gekoppeld aan één klant, met een rol. De website kan geen portaalgebruikers aanmaken.
  • E-mailadressen worden in kleine letters vergeleken, zonder spaties eromheen.
  • Eén adres mag aan meerdere klanten gekoppeld zijn. Een portaalgebruiker is het paar (e-mailadres, klant), dus dezelfde persoon kan bij twee bedrijven een koppeling hebben, elk met een eigen rol. Bij het aanmelden krijgt de sessie het laagste klantnummer dat tot het portaal is toegelaten; ReadSession en MyDetails noemen alle klanten waarvoor het adres mag optreden, en SwitchCustomer zet de sessie over naar een andere klant.
  • De portaalgebruiker moet actief zijn en mag niet geanonimiseerd zijn; de klant moet bestaan, mag niet vanwege privacy geblokkeerd zijn en mag niet op Alle geblokkeerd staan. Klanten die alleen op Verzenden of Factuur geblokkeerd staan, houden hun toegang.

Aanmelden met een eenmalige code

  1. De bezoeker vult zijn e-mailadres in; de website roept RequestCode(email, ipHash) aan.
  2. Business Central maakt een code, bewaart daar alleen een gezouten hash van en mailt de code naar het adres als het bij een actieve portaalgebruiker hoort.
  3. De bezoeker vult de code in; de website roept SignIn(email, code, ipHash) aan en krijgt een sessietoken, of "".
  4. De website bewaart het token, bijvoorbeeld in een HttpOnly-cookie, en geeft het als token mee aan elke andere procedure van voaPortal.
  5. SignOut(token) beëindigt de sessie.

De code bestaat uit tien tekens uit het alfabet 0123456789ABCDEFGHJKMNPQRSTVWXYZ (geen I, L, O of U) en staat in de e-mail als twee groepen van vijf met een koppelteken ertussen. Voor de vergelijking wordt hij genormaliseerd: spaties en koppeltekens eruit, alles in hoofdletters, O gelezen als 0, I en L als 1. Pas dezelfde regels toe als u de invoer op de website controleert.

  • Een code blijft geldig zolang Geldigheid aanmeldcode (min.) aangeeft, standaard 15 minuten, en werkt maar één keer. Na een geslaagde aanmelding verlopen alle andere open codes voor dat adres.
  • Verkeerd raden sluit niemand buiten. Mislukte pogingen worden geteld bij de nieuwste open code, tot Maximum codepogingen, als spoor om misbruik te herkennen.
  • Er kunnen meerdere codes tegelijk geldig zijn: een nieuwe code maakt eerdere niet ongeldig.
  • RequestCode antwoordt altijd true, ongeacht of het adres bekend, gekoppeld of geblokkeerd is en of de mail is mislukt, en duurt altijd minstens 1,5 seconde. Zo verklapt het endpoint aan niemand welke adressen van klanten zijn.
  • Tussen twee codes voor hetzelfde adres geldt een minimale wachttijd van Wachttijd aanmeldcode (sec.), standaard 60 seconden. Een aanvraag binnen die tijd krijgt true, maar er gebeurt niets.
  • De mail met de code vertrekt direct tijdens de aanroep van RequestCode, niet via de taakwachtrij. Hij is in de taal van de accountaanvraag waaruit de portaalgebruiker is ontstaan, anders in de taal van de klant, anders in die van de binnendienst.

Sessietokens

  • Een token bestaat uit 64 tekens: twee GUID's in hexadecimale hoofdletters. Behandel het als een ondoorzichtige waarde; Business Central bewaart alleen de SHA-256-hash ervan. Spaties voor en na token worden genegeerd.
  • Schuivende vervaltijd: een sessie verloopt als ze Sessieduur (min.) lang niet is gebruikt, standaard 480. Elke geslaagde aanroep verlengt de sessie; dat wordt hooguit één keer per minuut weggeschreven, dus een sessie kan tot een minuut eerder eindigen dan de nominale tijd.
  • Absolute grens: geen sessie blijft na het aanmelden langer bestaan dan Maximale sessieleeftijd (uren), standaard 168, hoe actief ze ook wordt gebruikt.
  • Bij elke aanroep wordt gecontroleerd of het token bestaat, niet is ingetrokken, niet is verlopen en binnen de maximale leeftijd valt, of de portaalgebruiker nog bestaat en actief is, en of de klant nog tot het portaal is toegelaten. Mislukt de controle op de portaalgebruiker of de klant, dan wordt de sessie meteen ingetrokken.
  • De rol wordt bij elke aanroep uit de portaalgebruiker gelezen, niet uit de sessie, dus een wijziging van Inkoper naar Meekijker geldt meteen.
  • ReadSession geeft expiresAt terug. Gebruik dat voor de levensduur van de cookie.
  • Als meerdere aanroepen tegelijk hetzelfde token gebruiken, kan Business Central op een ervan met 409 antwoorden. De lezende procedures kunt u veilig één keer opnieuw proberen.

Het klantslot

Alles wat voaPortal teruggeeft, is gefilterd op de klant van de sessie, en geen enkele parameter kan dat filter weghalen:

GegevensGefilterd op
Webaanvragen met hun regels en bijlagenKlantnr. van de aanvraag
Verkoopoffertes en -orders, actueel en gearchiveerdOrderklantnr.
Geboekte verzendingen, facturen, creditnota'sOrderklantnr.
LeveradressenKlantnr.
DXSteel Doc Archivede relatie is deze klant, het relatietype is Klant, en de rubriek is toegestaan

Een documentnummer is een parameter in DocumentLines, Document, AcceptQuote, AcceptQuoteWithDetails en RejectQuote, en een aanvraagnummer in RequestLines en MyAttachments. Het zegt wat er wordt gevraagd, nooit van wie het is. Documentnummers worden getrimd en omgezet naar hoofdletters; een nummer langer dan 20 tekens geldt als niet gevonden, niet als afgekapt.

Eén adres bij meerdere klanten

Een inkoper die voor twee bedrijven van dezelfde groep inkoopt, is in de handel heel gewoon. Zo iemand heeft twee koppelingen, elk met een eigen rol, en meldt zich één keer aan.

WatWaar
Voor welke klanten deze sessie mag optredencustomers in ReadSession en MyDetails
Welke klant nu actief iscustomerNo in ReadSession
De sessie naar een andere klant overzettenSwitchCustomer
Een koppeling maken of intrekkenDe binnendienst, in de Business Central-client. Er is geen webservice voor.

De actieve klant staat op de sessierij in Business Central, niet in een cookie. Daarop rust de hele beveiliging van deze functie: de website kan niet beslissen wiens gegevens ze ziet, ze kan alleen vragen om te wisselen naar een klant waarvan Business Central kan bevestigen dat hij aan het aangemelde adres gekoppeld is. Wisselen naar een ander klantnummer levert refused op, en er verandert niets.

SwitchCustomer is de enige procedure in het portaal die een klantnummer accepteert, en daarom zijn de regels eromheen strenger dan elders:

  • de koppeling (adres, klant) moet bestaan en actief zijn, en mag niet geanonimiseerd zijn;
  • de klant moet tot het portaal zijn toegelaten, met dezelfde controle als bij het aanmelden: niet vanwege privacy geblokkeerd en niet op Alle geblokkeerd;
  • de rol van de nieuwe sessie komt uit de koppeling waarnaar is gewisseld, niet uit de sessie. Dezelfde persoon kan bij het ene bedrijf Inkoper zijn en bij het andere Meekijker;
  • elke poging, geslaagd of geweigerd, wordt in Business Central in een logboek geschreven met het adres, beide klantnummers, de IP-hash en de reden. De website krijgt de reden nooit te horen.

Na een geweigerde wissel is de sessie onveranderd: ze leest nog steeds de gegevens van de klant waarop ze stond. Een half uitgevoerde wissel bestaat niet. En refused zegt nooit waarom, precies zoals overal in dit koppelvlak: met een reden zou dit endpoint een gratis manier worden om na te gaan welke klantnummers bestaan en wie eraan gekoppeld is.

Overal hetzelfde lege antwoord

Een document dat niet bestaat en een document van een andere klant geven precies hetzelfde antwoord. Anders zou een lijst nummers genoeg zijn om af te tasten wat er bestaat.

ProcedureGeen geldige sessieNiet gevonden, niet van u, niet toegestaan
RequestCodeniet van toepassingtrue (altijd)
SignInniet van toepassing"" (verkeerde, verlopen of gebruikte code, inactieve gebruiker)
SignOuttruetrue
ReadSession, MyDetails, MyRequests, MyQuotes, MyOrders, MyShipments, MyDocuments""niet van toepassing: een lijst mag leeg zijn
MyDeliveryAddresses"""" voor een Meekijker
LinkRequestfalsefalse
DocumentLines""""; een bestaand eigen document zonder artikelregels geeft {"lines":[]}
RequestLines""""; een eigen aanvraag zonder regels geeft {"requestNo":…,"lines":[]}
MyAttachments""""; een eigen aanvraag zonder bestanden geeft {"requestNo":…,"attachments":[]}
Attachment"""" (onbekende id, een id die niet van u is, of een bijlage zonder inhoud)
Document"""", ook als er geen rapport is ingesteld, of met het archief van Steel 365
AcceptQuote, AcceptQuoteWithDetails, RejectQuote{"outcome":"refused",…}{"outcome":"refused",…}
SwitchCustomer{"outcome":"refused",…}{"outcome":"refused",…}
MyNotificationSettings, SetNotificationSettings""niet van toepassing
InviteColleague{"outcome":"refused",…}{"outcome":"already"} bij een adres dat al toegang heeft; {"outcome":"throttled"} boven de daglimiet

Rollen

RolWaardeMag
InkoperBuyerAlles wat een Meekijker mag, plus LinkRequest, MyDeliveryAddresses, AcceptQuote, AcceptQuoteWithDetails, RejectQuote en InviteColleague.
MeekijkerViewerLezen: sessie, gegevens, aanvragen, aanvraagregels, offertes, orders, verzendingen, documentregels, documenten en bijlagen. Plus de eigen berichtvoorkeuren en SwitchCustomer.

Een nieuwe portaalgebruiker is Meekijker, tenzij de binnendienst Inkoper kiest, en een onbekende rolwaarde wordt gemeld als Viewer. Bij een Meekijker antwoordt LinkRequest met false, MyDeliveryAddresses met "" en AcceptQuote, AcceptQuoteWithDetails, RejectQuote en InviteColleague met refused. De berichtvoorkeuren en SwitchCustomer staan voor beide rollen open: juist een Meekijker wil weten dat er een verzending is geboekt, en de rol van een sessie komt uit de koppeling waarnaar is gewisseld. Een Meekijker kan wel een offerteaanvraag versturen via voaRequests; die wordt dan niet aan de klant gekoppeld, en de binnendienst behandelt die als een aanvraag van een anonieme bezoeker. MyDetails geeft role terug, zodat het formulier dat kan melden.

IP-hash

Verschillende aanroepen krijgen een ipHash mee. Business Central krijgt het IP-adres van de bezoeker nooit te zien: de website berekent een hash met een sleutel. De referentiewebsite gebruikt HMAC-SHA256 met een geheime sleutel uit haar eigen configuratie, als 64 hexadecimale tekens in kleine letters. Business Central slaat de waarde ongewijzigd op, tot 64 tekens, bij de aanmeldcode, de sessie, het akkoord op en de afwijzing van een offerte, de klantwissel en de aanvragen. Het gebruikt de waarde alleen om misbruik te herkennen, en de opruimtaak wist haar na de bewaartermijn. Houd de sleutel geheim en ongewijzigd: zonder sleutel is een IPv4-hash terug te rekenen.

Portaalinstellingen

Per bedrijf, op de pagina CustomerGate-instellingen:

InstellingStandaardToegestaanEffect
Geldigheid aanmeldcode (min.)151 t/m 1440Hoe lang een code geldig is.
Maximum codepogingen51 t/m 20Hoeveel mislukte pogingen per code worden geteld. Sluit niemand buiten.
Wachttijd aanmeldcode (sec.)600 t/m 3600Minimale tijd tussen twee codes voor één adres. 0 schakelt de wachttijd uit.
Sessieduur (min.)4805 t/m 20160Schuivende vervaltijd.
Maximale sessieleeftijd (uren)1681 t/m 480Het absolute einde van een sessie.
Portaaldocumenten uit rapportenaanZie MyDocuments en Document.
Portaalberichten ingeschakelduitDe hoofdschakelaar van het bedrijf voor de berichten uit MyNotificationSettings. Na een nieuwe installatie en na elke update staat hij uit, en uit betekent dat er niets in de wachtrij komt en niets wordt verstuurd, wat een portaalgebruiker ook heeft gekozen.
Maximum aantal uitnodigingen per dag50 t/m 100Hoeveel collega's één Inkoper per dag mag uitnodigen met InviteColleague. 0 schakelt uitnodigen uit; er wordt dan niet teruggevallen op de standaardwaarde.
Aanvragen weigerenuitLaat CreateQuoteRequest en CreateAccountRequest mislukken met een fout. Heeft geen invloed op het portaal.

Catalogusservices

De catalogusservices zijn alleen-lezen lijstpagina's, bedoeld voor een nachtelijke synchronisatie en niet voor live paginaweergaven. Gebruik $select om alleen de eigenschappen op te halen die u nodig hebt. De OData-eigenschapsnamen zijn de namen van de paginabesturingselementen hieronder en horen bij het contract. De waarden worden live uit de tabellen gelezen, ook de DXSteel-velden; die leest de app op veldnaam, zodat hij niet afhankelijk is van de DXSteel-app. Zonder Steel 365 geeft een DXSteel-tekstveld "" terug en een DXSteel-getal 0.

De feed bevat alleen wat op de website hoort

voaItems, voaVariants en voaCategories geven alleen gepubliceerde records terug. Welke artikelen dat zijn, wordt in Business Central beslist en niet door u: per artikel, per artikelcategorie, met publicatieregels in een vaste volgorde en een beleid op CustomerGate-instellingen. Een uitgesloten artikel zit helemaal niet in de feed, ook niet met een vlag of met een lege omschrijving. Het filter zit in de query en is met $filter niet weg te halen.

  • Blocked en SalesBlocked blijven in voaItems staan en zijn altijd false. Een geblokkeerd artikel wordt nooit gepubliceerd. De twee eigenschappen blijven bestaan voor compatibiliteit; haal uw afhandeling ervan niet weg, maar gebruik ze ook niet om artikelen van de website te weren.
  • De feed is leidend, maar een feed zegt niets over wat er niet in staat. Een artikel dat verdwijnt, komt niet meer langs, dus een upsert laat het gewoon staan. Daar is voaRemovals voor: alles wat u hebt en wat niet in de feed zit maar wel in voaRemovals verschijnt, moet weg.
  • Voor foto's geldt dezelfde regel: van een artikel dat niet in de feed zit, krijgt u ook geen foto.

Een bedrijf waarvoor de beslissing nog nooit is berekend, krijgt helemaal niets: de drie cataloguspagina's en voaCatalogMedia mislukken dan met [VOA-CatalogNotComputed] in plaats van een ongefilterde catalogus terug te geven. De app berekent de beslissing bij de installatie, bij het aanmaken van een bedrijf en bij elke update, dus dit hoort niet voor te komen. Gebeurt het toch, stop dan de synchronisatie en houd wat u hebt: een weigering is geen lege catalogus. De beheerder kiest Webcatalogus herberekenen op CustomerGate-instellingen.

voaItems

Brontabel: Artikel. Er worden geen prijzen, kosten of voorraadwaarden meegegeven.

EigenschapTypeBron
NostringNr.
DescriptionstringOmschrijving
Description2stringOmschrijving 2
BaseUnitofMeasurestringBasiseenheid
DXPriceUOMSalesDMXstringDXSteel Price UOM (Sales) DMX
ItemCategoryCodestringArtikelcategoriecode
DXItemProfileCodeDMXstringDXSteel Item Profile Code DMX
DXQualityCodeDMXstringDXSteel Quality Code DMX, de kwaliteit
BlockedbooleanGeblokkeerd. Altijd false: een geblokkeerd artikel wordt niet gepubliceerd.
SalesBlockedbooleanVerkoop geblokkeerd. Altijd false, om dezelfde reden.
webQuotablebooleanNiet online bestelbaar, omgekeerd. false betekent dat het artikel wel in de catalogus staat maar niet in een aanvraag mag: prijs op aanvraag, bel ons, of er is eerst een tekening nodig. Toon zo'n artikel zonder bestelknop; voaRequests weigert een regel met dit artikel. Let op de kleine w.
pictureCountnumberHoeveel foto's het artikel in Business Central heeft.
pictureIdstringEen kenmerk van de huidige foto's: 64 hexadecimale tekens, of "" als het artikel geen foto heeft. Zie Foto's hieronder.

voaVariants

Brontabel: Artikelvariant. Een variant volgt zijn artikel: varianten van een artikel dat niet gepubliceerd is, staan niet in deze feed.

EigenschapTypeBron
ItemNostringArtikelnr.
CodestringCode
DescriptionstringOmschrijving
Description2stringOmschrijving 2
DXLengthnumberDXSteel Length DMX
DXWidthnumberDXSteel Width DMX
DXThicknessnumberDXSteel Thickness DMX
DXExtraLengthnumberDXSteel Extra Length DMX
DXFormatTypestringDXSteel Format Type DMX, als bijschrift in de taal van de sessie. Stuur een vaste Accept-Language mee als u deze bijschriften omzet naar uw eigen model.
BlockedbooleanGeblokkeerd. Dit is de eigen vlag van de variant, en daar wordt niet op gefilterd: de website beslist.
DXRemainingInventorynumberDXSteel Remaining Inventory DMX, opgeteld over alle locaties
pictureCountnumberHoeveel eigen foto's deze variant heeft. 0 betekent geen eigen foto, niet geen foto: val dan terug op die van het artikel.
pictureIdstringHetzelfde soort kenmerk als op voaItems, voor de eigen foto's van deze variant.

voaCategories

Brontabel: Artikelcategorie, met de eigenschappen Code en Description. Er is geen veld voor de bovenliggende categorie; de referentiewebsite leidt de boom af uit de code (BALK-IPE hoort onder BALK). Een artikelcategorie die zelf of via een bovenliggende categorie is uitgesloten, staat niet in deze feed, zodat een uitgesloten tak geen leeg menu-item op de website achterlaat.

voaRemovals

Brontabel: Webcatalogus - verwijderingen. Alles wat uit de catalogus is verdwenen en niet is teruggekomen. Lees deze service in dezelfde ronde als de andere drie, anders blijft een artikel dat u in Business Central hebt uitgesloten voorgoed op de website staan.

EigenschapTypeWat het is
KindstringItem, Variant of Category: een vaste waarde, nooit vertaald
NostringHet artikelnummer, of de code van de artikelcategorie
VariantCodestringDe variant, bij Kind = Variant; anders ""
RemovedAttijdstempelWanneer het record uit de catalogus verdween

Verder niets: geen omschrijving, geen prijs, geen reden. Een uitgesloten artikel is juist iets dat de buitenwereld niet hoort te zien. Vraag alles op sinds uw vorige geslaagde ronde, verwijder die records of haal ze offline, en bewaar het tijdstip van de ronde. U hoeft geen bevestiging terug te sturen: het filter op RemovedAt is het hele protocol.

GET .../ODataV4/voaRemovals?$filter=RemovedAt gt 2026-09-19T02:00:00Z&$orderby=RemovedAt
  • Een rij verdwijnt zodra het record weer in de catalogus komt. Een record dat weer in de feed staat, gaat voor op een verwijderrij die u eerder hebt opgehaald, dus verwerk eerst de feed en daarna de verwijderingen, of controleer ze nog eens aan de hand van de feed.
  • Er worden rijen geschreven voor een uitsluiting, voor een blokkering, voor een verwijderd artikel, een verwijderde variant of een verwijderde categorie, en voor het oude nummer na een nummerwijziging (het nieuwe nummer staat in de feed).
  • Een Variant-rij wordt alleen geschreven als de variant verdwijnt terwijl het artikel blijft. Verdwijnt het artikel, dan dekt zijn eigen rij de varianten: haal het artikel met alles eronder weg.
  • Rijen ouder dan 180 dagen worden opgeruimd. Een website die een half jaar niet heeft gesynchroniseerd, kan niet op deze lijst vertrouwen en moet haar hele catalogus één keer met de feed vergelijken.

Foto's

De foto's van een artikel staan in Business Central in een MediaSet, en OData kan hun bytes niet via een pagina-eigenschap doorgeven. De catalogus verdeelt het werk daarom: de feed zegt of er iets op te halen valt en of het is veranderd, en een actie geeft telkens één foto terug.

WatWaar
Heeft dit artikel foto's, en zijn ze veranderd?pictureCount en pictureId op voaItems
Heeft deze variant eigen foto's?pictureCount en pictureId op voaVariants
De bytes van één fotovoaCatalogMedia_Picture

pictureId is een kenmerk, geen naam. Het is een SHA-256-hash van de id's van de foto's in de set, op volgorde. De id van de MediaSet zelf is hiervoor niet bruikbaar: die identificeert de set en kan gelijk blijven terwijl de foto's erin worden vervangen, zodat een synchronisatie die erop vertrouwt, de nieuwe foto nooit zou ophalen. Vergelijk pictureId met wat u hebt bewaard; wijkt het af, haal de foto's dan opnieuw op. Is het "", dan heeft het artikel geen foto.

Een foto die in Business Central wordt verwijderd, komt binnen als een lege pictureId, in de volgende ronde van dezelfde feed die u toch al leest. Hij staat bewust niet in voaRemovals: die service gaat over records die uit de catalogus zijn verdwenen, en als daar ook wijzigingen op veldniveau in kwamen, zou u twee mechanismen voor hetzelfde op elkaar moeten afstemmen. Verwijder uw kopie als pictureId van gevuld naar leeg gaat, en als het artikel zelf in voaRemovals verschijnt.

voaCatalogMedia

voaCatalogMedia_Picture geeft één foto terug. De parameters zijn tekenreeksen, zoals overal:

ParameterToelichting
itemNoHet artikelnummer. Wordt getrimd en omgezet naar hoofdletters.
variantCodeDe variantcode voor een variantfoto, of "" voor de foto van het artikel zelf.
pictureIndex"1" tot en met pictureCount, als tekenreeks. Een lege of onleesbare waarde telt als 1.
SleutelTypeToelichting
itemNostring
variantCodestring"" bij een artikelfoto.
pictureIndexnumberWelke foto dit is.
pictureCountnumberHoeveel foto's er zijn, zodat u ze kunt doorlopen zonder terug te gaan naar de feed.
pictureIdstringHetzelfde kenmerk als in de feed; vergelijk het met de waarde waarop u het ophalen baseerde.
contentTypestringimage/jpeg, image/png enzovoort, zoals Business Central het heeft opgeslagen; application/octet-stream als Business Central het niet weet.
fileNamestring{item}-{index}.{ext} of {item}-{variant}-{index}.{ext}, afgeleid van het inhoudstype.
byteSizenumberDe grootte van de foto in bytes, vóór base64.
base64stringDe bytes. Ontbreekt als de foto boven de limiet uitkomt of niet te lezen is.
tooLargebooleanAlleen aanwezig, en dan true, als de foto boven de limiet uitkomt.
unreadablebooleanAlleen aanwezig, en dan true, als Business Central de foto helemaal niet kan laden. byteSize is dan 0.

De service geeft "" terug, hetzelfde lege antwoord als overal, als het artikel niet bestaat, niet in de webcatalogus staat, de variant er niet bij hoort, of die foto er niet is. Voor een uitgesloten artikel komt er geen foto: anders zou een geraden artikelnummer alsnog de foto van iets interns opleveren. In een bedrijf waar de catalogus nooit is berekend, mislukt ook deze service met [VOA-CatalogNotComputed].

De limiet is Maximale fotogrootte (MB) op CustomerGate-instellingen, standaard 10 MB. Daarboven krijgt u het hele antwoord zonder base64 en met tooLarge: true. Dat is met opzet geen fout: één foto rechtstreeks uit een camera mag geen nachtronde afbreken. Een foto die Business Central niet kan laden, komt op dezelfde manier terug, met unreadable: true en zonder base64. Behandel beide gevallen hetzelfde: sla die ene foto over, meld hem en ga verder. Lees unreadable niet als "dit artikel heeft geen foto": pictureCount telt hem nog mee, en de foto in Business Central moet worden vervangen.

Een nachtronde met foto's

  1. Haal voaItems op met pictureId en pictureCount erbij, naast de kolommen die u toch al synchroniseert.
  2. Haal voor elk artikel waarvan pictureId afwijkt van wat u hebt bewaard, de foto's één voor één op met voaCatalogMedia_Picture, van index 1 tot en met pictureCount, en bewaar pictureId naast de bestanden. Is pictureId leeg geworden, verwijder dan uw kopie.
  3. Doe hetzelfde voor voaVariants, met variantCode gevuld.
  4. Haal alleen op wat is veranderd. Een catalogus van tienduizend artikelen met foto's beslaat gigabytes; een nachtronde die ze allemaal ophaalt, raakt nooit klaar. Daar is pictureId voor: de tweede nacht kost bijna niets.
  5. Eén foto per aanroep. Er is geen batchverwerking: base64 maakt de payload een derde groter, en Business Central beantwoordt toch maar één webserviceaanroep tegelijk. Loop de lijst af en stop als uw tijdsbudget op is; dankzij de kenmerken gaat de volgende ronde verder waar u bent gestopt.
  6. Er wordt niets verkleind. U krijgt precies wat in Business Central staat, in de oorspronkelijke grootte en indeling. Maak zelf uw miniaturen en webformaten.

voaProfiles, voaGrades, voaItemTypes

Deze drie wijzen naar pagina's van iFacto Steel 365 (DXSteel), niet naar pagina's van deze app. Hun eigenschappen zijn van iFacto en kunnen met een update van iFacto veranderen; deze naslag legt ze niet vast. Ze bestaan alleen waar Steel 365 is geïnstalleerd, en de webcatalogus filtert ze niet: het zijn basisgegevens, geen artikelen. Stelt u er een filterlijst mee samen, baseer die dan op de artikelen die u werkelijk hebt ontvangen.

voaRequests: offerte- en accountaanvragen

Drie acties. Twee ervan krijgen de hele aanvraag als JSON in één tekstparameter payload en geven {"number":"…"} als tekst terug; de derde voegt een bestand toe aan een aanvraag en wordt beschreven onder Bijlagen hieronder. Het websiteaccount kan aanvragen aanmaken, maar niet teruglezen: er is geen service die aanvragen opsomt of een klant op e-mailadres opzoekt.

ActieParametersGeeft terug
voaRequests_CreateQuoteRequestpayloadstring: {"number":"WEB000123"}
voaRequests_CreateAccountRequestpayloadstring: {"number":"ACC000045"}
voaRequests_AddAttachmentrequestNo, fileName, contentBase64string: {"ok":true,"attachmentId":10000,…}

Hoe de payload wordt gelezen: een ontbrekende sleutel en een JSON-null betekenen allebei leeg ("", 0 of false); tekstwaarden worden zonder melding afgekapt op de veldlengte; Code-velden worden in hoofdletters opgeslagen; de aanroep loopt in één transactie, dus als er iets mislukt, wordt er niets opgeslagen. Business Central vult zelf het aanvraagnummer, de status New, het tijdstip van ontvangst, de gekoppelde klant en bij accountaanvragen de beoordelaar en de reden van afwijzing in; de payload doet dat nooit.

CreateQuoteRequest: sleutels in de kop

SleutelTypeVerplichtMaxToelichting
requestIdstringaanbevolen64Sleutel tegen dubbele verwerking, in hoofdletters. Ook de enige manier waarop LinkRequest de aanvraag kan vinden.
privacyAcceptedbooleanja, trueZonder deze sleutel wordt de aanvraag geweigerd.
privacyConsentVersionstringja20Welke privacyverklaring is geaccepteerd. In hoofdletters.
contactNamestring100Contactpersoon.
emailstring100Waar de aanvrager de offerte verwacht.
companyNamestring100
vatRegistrationNostring20Wordt niet gecontroleerd.
phoneNostring30
customerReferencestring35De eigen referentie van de klant. Wordt Extern documentnr. op de offerte.
projectNamestring100Sinds 1.0.44. De bouwplaats of het project waarvoor het staal is, bijvoorbeeld "Nieuwbouw Hoeve" of het adres van een bouwplaats. Wordt de eerste tekstregel van de offerte, Project: <naam>; zie Project, keuringsdocument en bewerking hieronder. Persoonsgegeven: na de bewaartermijn gewist, uit de aanvraag en uit de offerte.
languageCodestring10Een taalcode van Business Central, bijvoorbeeld NLB of FRB. De bevestigingsmail wordt in die taal geschreven.
address, address2, postCode, city, countryCodestring100 / 50 / 20 / 30 / 10Het factuuradres van de aanvrager. Het land is een ISO-code, bijvoorbeeld BE.
deliveryMethodstringPickup of Delivery. Elke andere waarde wordt Pickup.
shipToCodestring10Een geregistreerd leveradres van de klant, uit MyDeliveryAddresses. Telt pas mee nadat LinkRequest het heeft gecontroleerd.
useShipToAddressbooleanHet leveradres wijkt af van het factuuradres.
shipToName, shipToAddress, shipToAddress2, shipToPostCode, shipToCity, shipToCountryCodestring100 / 100 / 50 / 20 / 30 / 10Het leveradres als tekst. Een ander land van levering kan de btw-behandeling van de offerte veranderen.
requestedDeliveryDatestringYYYY-MM-DD. Een waarde die geen geldige datum is, laat de aanroep mislukken met [VOA-InvalidInput]. Laat de sleutel weg als er geen datum is; een lege tekenreeks mag ook.
remarksstring2048Vrije tekst van de aanvrager.
ipHashstring64Zie IP-hash hierboven.
sourcestring50Het kanaal of de pagina. staffOrder heeft een bijzondere betekenis; zie hieronder.
assistedBystring50De medewerker die een order via medewerker invoerde. Alleen bij een order via medewerker.
quoteRequestLinesarrayRegelobjecten. Ontbreekt de sleutel of is het geen array, dan heeft de aanvraag geen regels. Elementen die geen object zijn, worden overgeslagen.

CreateQuoteRequest: sleutels per regel

Objecten in quoteRequestLines, genummerd 10000, 20000, … in de volgorde van de array:

SleutelTypeMaxToelichting
itemNostring20Of het artikel bestaat, wordt bij het invoegen niet gecontroleerd; dat gebeurt als de binnendienst de aanvraag omzet. Of het mag worden aangevraagd, wordt wel gecontroleerd: een regel voor een artikel dat niet in de webcatalogus staat of niet online bestelbaar is, wordt geweigerd. Leeg bij een regel met vrije tekst, die altijd wordt geaccepteerd.
variantCodestring10De afmeting.
profileCodestring30Profiel uit Steel 365.
qualityCodestring20Kwaliteit uit Steel 365.
descriptionstring100Zoals op de website getoond, of vrije tekst.
quantitynumberHet gevraagde aantal.
unitOfMeasureCodestring10Bijvoorbeeld PCS, M of KG. Wordt bij de omzetting gecontroleerd.
lengthMmnumber0 betekent de standaardlengte van de afmeting.
widthMmnumberAlleen platen en strip.
thicknessMmnumberAlleen platen en strip.
lineRemarksstring250Bijvoorbeeld een zaaginstructie.
inspectionCertificatestringSinds 1.0.44. Het keuringsdocument volgens EN 10204 dat bij deze regel wordt gevraagd: None (de standaard), Certificate22 (testrapport 2.2), Certificate31 of Certificate32 (inspectiecertificaat 3.1 of 3.2). Hoofdletters tellen niet. Elke andere waarde, zoals een onbekende naam, een getal als 31 of een object, wordt None en laat de aanroep niet mislukken.
processingarray van tekstSinds 1.0.44. De bewerkingen die bij deze regel worden gevraagd, zoveel als nodig uit: CutToLength, MitreCutting, Drilling, Punching, ThermalCutting (laser, plasma of autogeen), Bending, Blasting, Priming, Galvanizing (thermisch verzinken), Painting. Hoofdletters tellen niet. Onbekende waarden en elementen die geen tekst zijn, worden overgeslagen, dezelfde waarde twee keer wordt één keer opgeslagen, en een processing die geen array is, wordt genegeerd. Niets hiervan laat de aanroep mislukken.

Een voorbeeldpayload, voordat die in payload wordt geserialiseerd:

{
  "requestId": "3F2C9A1E7B0D4C5E",
  "languageCode": "NLB",
  "contactName": "Jan Example",
  "companyName": "Example Ltd",
  "email": "jan@example.com",
  "customerReference": "Site 12",
  "projectName": "New build Hoeve, Kerkstraat 12",
  "address": "Example Street 1",
  "postCode": "2000",
  "city": "Antwerp",
  "countryCode": "BE",
  "deliveryMethod": "Delivery",
  "useShipToAddress": true,
  "shipToName": "Example Ltd",
  "shipToAddress": "Yard Road 5",
  "shipToPostCode": "2100",
  "shipToCity": "Deurne",
  "shipToCountryCode": "BE",
  "requestedDeliveryDate": "2026-10-01",
  "remarks": "Please call before delivery.",
  "privacyAccepted": true,
  "privacyConsentVersion": "2026-09",
  "ipHash": "<64 hex characters>",
  "source": "shop/en/quote-request",
  "quoteRequestLines": [
    {
      "itemNo": "1000",
      "variantCode": "6000",
      "quantity": 4,
      "unitOfMeasureCode": "PCS",
      "lineRemarks": "Cut to 3 m",
      "inspectionCertificate": "Certificate31",
      "processing": ["CutToLength", "Drilling", "Galvanizing"]
    }
  ]
}

Wat er daarna gebeurt:

  • De aanvraag komt in een eigen tabel terecht, niet in een verkoopofferte. De binnendienst zet hem met één actie om, alleen vanuit de Business Central-client.
  • De mailtaak stuurt de aanvrager een bevestiging en de binnendienst een melding, afhankelijk van de schakelaars in de instellingen.
  • source = staffOrder markeert een order via medewerker: een lijst die een medewerker invoert voor een klant die er zelf is. Die wordt omgezet naar een verkooporder in plaats van een offerte, mag elk artikel bevatten dat het bedrijf verkoopt, en er wordt geen bevestiging of melding verstuurd. Bij afhalen krijgt de order een tekstregel met de naam van de medewerker uit assistedBy. Na de installatie staan orders via medewerker uit: zolang Orders via medewerker ingeschakeld uit staat, wordt de aanroep geweigerd met StaffOrdersDisabled en wordt er niets opgeslagen. Versie 1.0.39 gebruikte hiervoor counterVisit.
  • Is de bezoeker aangemeld, roep dan meteen na deze aanroep LinkRequest aan om de klant van de sessie op de aanvraag te zetten. Het klantnummer zit nooit in de payload.
  • shipToCode telt pas mee nadat LinkRequest de code aan de hand van de klant van de sessie heeft gecontroleerd. Bij een aanvraag die niet op die manier is gekoppeld, weigert de omzetting de code tot de binnendienst hem met de hand bevestigt; stuur het adres daarom ook als tekst mee.

Project, keuringsdocument en bewerking

Sinds 1.0.44. Tot deze versie kon een website een bouwplaats, een 3.1-certificaat of "zagen, boren, verzinken" alleen in remarks en lineRemarks schrijven. De drie sleutels brengen ze als gegevens binnen: de binnendienst ziet het project op de aanvraagkaart en in de werklijst, en het keuringsdocument en de bewerking als kolommen op de aanvraagregels. Schrijf ze niet meer in de opmerkingen zodra u de sleutels stuurt, anders staan ze twee keer op de offerte.

Bij het omzetten komen ze als tekstregels op de verkoopofferte, of bij een order via medewerker op de verkooporder. Zo staan ze op het document dat de klant krijgt en op de werkbon van wie het staal zaagt:

SleutelWaar op de offerteTaal
projectNameDe eerste regel van het document: Project: <naam>, verdeeld over meer regels als de tekst langer is dan één regel.Altijd Project: …, in elke taal. De opruimtaak zoekt deze regel woordelijk terug om hem te wissen, en een vertaalde regel zou ze misschien niet vinden.
inspectionCertificateEen tekstregel onder de artikelregel waar het bij hoort, bijvoorbeeld "Inspectiecertificaat 3.1 (EN 10204)".De taal van de offerte, uit de Taalcode ervan.
processingEen tekstregel daaronder, bijvoorbeeld "Bewerking: Op lengte zagen, Boren, Thermisch verzinken".De taal van de offerte.

Ze staan vóór de eigen lineRemarks van de regel. Er wordt geen veld van de verkoopkop beschreven: Uw referentie bevat het aanvraagnummer en Extern documentnr. de customerReference, en Business Central heeft geen standaardveld voor een bouwplaats. Een keuringsdocument en een bewerking zijn geen persoonsgegevens en blijven op de offerte staan; het project wel, en de opruimtaak wist het uit de aanvraag en uit de offerte, net als het leveradres.

Het project staat ook in de twee mails over de aanvraag: de bevestiging aan de aanvrager heeft een regel met het project, met een label in de taal van de aanvraag, en de melding aan de binnendienst een rij Project. Zonder project staat het in geen van beide, en het keuringsdocument en de bewerking staan niet in de mails. De controle telt links in het project mee met die in remarks; de controle op een lange tekst zonder cijfer blijft bij remarks, omdat een project hooguit 100 tekens heeft. MyRequests en RequestLines geven het project terug.

Regels die worden geweigerd

Een regel voor een artikel dat niet in de webcatalogus staat, laat de hele aanroep mislukken met [VOA-ItemNotOnWebsite]; een regel voor een artikel dat wel in de catalogus staat maar niet online bestelbaar is (webQuotable is false), met [VOA-ItemNotQuotable]. Er wordt dan niets opgeslagen. Het eerste is meestal een verouderde pagina: ververs de catalogus en verwijder de regel. Het tweede hoort bij een artikel dat u als "prijs op aanvraag" toont, zonder bestelknop.

Een order via medewerker is hiervan uitgezonderd: bij source = staffOrder worden de twee catalogusweigeringen niet toegepast, omdat met de klant erbij alles verkocht mag worden wat het bedrijf verkoopt, ook wat bewust niet online staat.

CreateAccountRequest

Een aanvraag voor een portaalaccount. De binnendienst koppelt hem aan een klant en een rol, of wijst hem af.

SleutelTypeVerplichtMaxToelichting
requestIdstringaanbevolen64Sleutel tegen dubbele verwerking, in hoofdletters.
privacyAcceptedbooleanja, true
privacyConsentVersionstringja20In hoofdletters.
emailstringja100Het adres waarmee de persoon zich zal aanmelden.
contactNamestring100
jobTitlestring50Helpt de binnendienst te kiezen tussen Inkoper en Meekijker.
companyNamestring100Wordt niet gecontroleerd.
vatRegistrationNostring20
phoneNostring30
address, address2, postCode, city, countryCodestring100 / 50 / 20 / 30 / 10
existingCustomerstringYes, No of Unknown. Elke andere waarde wordt Unknown.
statedCustomerNostring20Het klantnummer dat de persoon opgeeft. Een aanwijzing voor de binnendienst, nooit een koppeling.
languageCodestring10Wordt gebruikt voor de mails aan deze persoon, ook voor latere aanmeldcodes.
remarksstring2048
ipHashstring64
sourcestring50De pagina waar de aanvraag vandaan kwam.

Dubbele aanvragen voorkomen: requestId

Genereer per verzonden formulier één requestId en stuur dezelfde waarde mee als u die verzending opnieuw probeert. Een tweede aanvraag met een requestId die al bestaat, wordt geweigerd met HTTP 400, en de foutmelding bevat het merkteken [VOA-DUBBEL], de code DuplicateRequest en het nummer van de aanvraag die al is opgeslagen. Beschouw dat als geslaagd: de aanvraag staat in Business Central, alleen niet dankzij deze aanroep. De tekst van de melding wordt vertaald, dus controleer op het merkteken en nooit op de zin. De vergelijking is in feite niet hoofdlettergevoelig, omdat de waarde in hoofdletters wordt opgeslagen; zonder requestId is er geen controle op dubbele aanvragen. De regel geldt voor beide acties, elk binnen de eigen tabel.

Controle na ontvangst

Zodra een aanvraag is opgeslagen, controleert Business Central die: is het een herhaling van een eerdere aanvraag van hetzelfde adres binnen de ingestelde Termijn dubbele aanvragen (uren), en in hoeverre lijkt de aanvraag op ongewenst verkeer? Daarvan merkt de website niets. Het antwoord en de statuscode blijven gelijk, en de screening kan de aanroep nooit laten mislukken. Een aanvraag die op spam lijkt, wordt opgeslagen, met haar nummer beantwoord en net als elke andere aan de binnendienst getoond; ze wordt nooit geweigerd, verborgen of verwijderd. Bouw op de website dus niets dat voor een gemarkeerde aanvraag een ander antwoord verwacht: dat komt er niet. De ipHash doet hier wel ter zake: zonder die waarde kan het signaal over het aantal aanvragen per adres niet worden geteld. Hij blijft wat hij was: een pseudonieme HMAC die de website maakt; Business Central ziet nooit een IP-adres.

Het btw-nummer in MyDetails

Als u het offerteaanvraagformulier vooraf invult met MyDetails, krijgt u het btw-nummer van de klant terug onder de sleutel vatRegistrationNo, uit het veld waarin deze lokalisatie het bewaart: Btw-nummer, of bij een binnenlandse klant in een Belgische installatie Ondernemingsnummer (veld 11310 op de Klantenkaart, dat alleen in de Belgische lokalisatie bestaat). De sleutel en de betekenis ervan blijven gelijk; alleen het veld waaruit Business Central het nummer leest, verschilt per land.

Fouten

Alle fouten zijn AL-fouten, dus de aanroep mislukt met HTTP 400 en de melding in de body. De aanvraag wordt niet opgeslagen. Elke melding begint met een merkteken [VOA-…] dat niet wordt vertaald; zie Foutcodes verderop.

VoorwaardeGeldt voorCodeDe melding begint met
payload is geen geldige JSONbeideInvalidInputThe request is not valid JSON.
Aanvragen weigeren staat aanbeideRequestsClosedCustomerGate is not accepting requests at the moment.
privacyAccepted is niet truebeidePrivacyConsentMissingA request cannot be accepted without agreement…
privacyConsentVersion is leegbeidePrivacyConsentMissingInclude in privacyConsentVersion…
email is leegaccountaanvragenInvalidInputAn account request without an e-mail address is not possible…
requestId is al gebruiktbeideDuplicateRequestThis submission has already been accepted under number… (bevat ook [VOA-DUBBEL])
Een regel bevat een artikel dat niet in de webcatalogus staatofferteaanvragenItemNotOnWebsiteItem … is not in the web catalog…
Een regel bevat een artikel dat niet online bestelbaar isofferteaanvragenItemNotQuotableItem … is available on request only…
requestedDeliveryDate is geen geldige datumofferteaanvragenInvalidInputThe value of requestedDeliveryDate in the request is not valid.
source is staffOrder terwijl Orders via medewerker ingeschakeld uit staatalleen offerteStaffOrdersDisabledStaff orders are switched off in this company…
Geen nummerreeks in dit bedrijfbeideSetupIncompleteCustomerGate is not set up in this company…

projectName, inspectionCertificate en processing (sinds 1.0.44) geven geen fout: een te lang project wordt net als elke andere tekst op 100 tekens afgekapt, en een waarde die geen van de waardenamen is, valt weg. De aanvraag zelf is de boodschap.

Bijlagen bij een aanvraag

Een tekening, een DXF, een foto van het oude onderdeel of een zaaglijst. Uploaden gaat via voaRequests, teruglezen via voaPortal: dat verschil is geen slordigheid, maar de kern van het ontwerp.

AddAttachment

Deze actie staat op voaRequests en niet op voaPortal, omdat een aanvraag van iedereen kan komen, ook van een bezoeker die niet is aangemeld, en juist van die bezoeker zou de binnendienst de tekening anders per e-mail moeten opvragen. Teruglezen gaat wel via voaPortal, waar een sessie beschikbaar is om het eigendom mee te controleren.

ParameterToelichting
requestNoHet aanvraagnummer (WEB000123) of de requestId van de verzending. Gebruik bij voorkeur de requestId: een aanvraagnummer loopt op en is dus te raden.
fileNameNaam met extensie, zonder map. Een voorafgaand pad wordt verwijderd; een naam die daarna nog een backslash, een schuine streep of een dubbele punt bevat, wordt geweigerd. Er worden hooguit 250 tekens bewaard.
contentBase64Het bestand, base64-gecodeerd. Verder niets: een inhoudstype dat u meestuurt, zou toch niet worden vertrouwd.

Het antwoord, na het ontleden van value:

SleutelTypeToelichting
okbooleanAltijd true; bij een mislukking krijgt u een fout, geen ok:false.
attachmentIdnumberHet regelnummer binnen de aanvraag (10000, 20000, …). Voor uw eigen administratie; Attachment op voaPortal gebruikt in plaats daarvan de id uit MyAttachments.
fileNamestringDe naam zoals Business Central hem heeft opgeslagen, zonder eventuele map.
sizenumberDe grootte in bytes, gemeten na het decoderen.
contentHashstringDe SHA-256-hash van de inhoud, 64 hexadecimale tekens in kleine letters.
  • Twee keer dezelfde inhoud levert één bijlage op. Een tweede aanroep met dezelfde bytes voor dezelfde aanvraag geeft dezelfde attachmentId terug en slaat verder niets op, dus na een time-out kunt u een upload veilig opnieuw proberen. De naam van de eerste upload blijft staan.
  • Status: geaccepteerd zolang de aanvraag New of InProgress is. Geweigerd zodra ze Converted, Rejected of Spam is, en geweigerd voor een aanvraag waarvan de persoonsgegevens zijn gewist.
  • De grootte wordt twee keer gecontroleerd: eerst op de lengte van de base64-tekenreeks, vóór het decoderen, zodat een enorme payload de sessie niet kan uitputten, en daarna op de echte bytes.
  • Het inhoudstype wordt nooit van u overgenomen. CustomerGate leidt het af uit de extensie; een onbekende extensie krijgt application/octet-stream.

De instellingen, en wat geen instelling kan wijzigen

Vijf velden op CustomerGate-instellingen, onder Bijlagen, bepalen wat AddAttachment accepteert. Ze gelden per bedrijf, dus neem de standaardwaarden hieronder niet over in uw formulier. Sinds 1.0.44 geeft voaPing onder attachments de waarden die in dit bedrijf gelden: of uploaden überhaupt werkt, de grenzen voor grootte en aantal met de terugvalwaarde voor 0 al toegepast, de toegestane soorten precies zoals AddAttachment ze controleert, de weigerlijst en de soorten waarvan de eerste bytes worden gecontroleerd. Ze komen uit dezelfde regels als de controle bij het uploaden, dus een formulier dat erop is gebouwd, accepteert precies wat Business Central accepteert. Lees ze als uw toepassing start, samen met de rest van voaPing. Vóór 1.0.44: vraag de beheerder hoe ze zijn ingesteld voordat u een klant iets belooft.

InstellingStandaardToegestaanEffect
Bijlagen ingeschakeldaanAls dit uit staat, mislukt AddAttachment met een fout. Bestanden die al zijn opgeslagen, blijven leesbaar via MyAttachments en Attachment.
Maximale grootte bijlage (KB)102400 t/m 20480Per bestand, gemeten op de gedecodeerde bytes. Bij 0 geldt 10240.
Toegestane soorten bijlagenpdf,png,jpg,jpeg,dxf,dwg,step,stp,xlsx,csvExtensies zonder punt, gescheiden door komma's. Alle andere worden geweigerd.
Maximum aantal bijlagen per aanvraag100 t/m 100Per aanvraag. Bij 0 geldt 10.
Bijlagen kopiëren naar verkoopdocumentaanOf de omzetting de bestanden naar de bijlagen van het verkoopdocument kopieert. Niet zichtbaar via een webservice.

De weigerlijst exe, dll, js, vbs, ps1, bat, cmd, scr, msi, jar, sh, html, htm, svg staat in de code, wordt toegepast op elk deel van de naam tussen punten (drawing.exe.pdf wordt geweigerd) en gaat voor op Toegestane soorten bijlagen. Geen enkele instelling kan die lijst opheffen. Hetzelfde geldt voor de controle op de eerste bytes: bij pdf, png, jpg, jpeg, xlsx, dwg, step en stp moet de inhoud beginnen zoals de extensie belooft. dxf en csv hebben geen vast begin en worden niet op inhoud gecontroleerd.

CustomerGate controleert het bestandstype, niet de integriteit. Een bestand waarvan de eerste bytes bij de extensie passen, wordt opgeslagen en precies zo teruggegeven als het binnenkwam, ook als het afgekapt of anderszins beschadigd is, net als een bestand dat per e-mail binnenkomt. Business Central mag de inhoud met opzet niet interpreteren, zodat een beschadigde afbeelding nooit midden in een upload tot een platformfout kan leiden. Wilt u meer dan een typecontrole, valideer het bestand dan op de website voordat u AddAttachment aanroept.

MyAttachments

voaPortal_MyAttachments(token, requestNo) geeft de bestanden terug van één offerteaanvraag van de klant achter deze sessie: wat de klant zelf heeft gestuurd, plus wat de binnendienst heeft toegevoegd. Het eigendom wordt op de aanvraag gecontroleerd, want een bijlage heeft geen klantnummer: de aanvraag moet van de klant van deze sessie zijn en mag niet geanonimiseerd of als spam gemarkeerd zijn. Dat zijn dezelfde drie voorwaarden als bij MyRequests, zodat een klant nooit in het ene scherm ziet wat in het andere is verdwenen. Een aanvraag van iemand anders geeft hetzelfde "" als een aanvraag die niet bestaat.

SleutelTypeToelichting
requestNostringDe aanvraag, ongewijzigd teruggegeven.
attachmentsarrayDe bestanden, de oudste eerst.

Elk element:

SleutelTypeToelichting
idstringDe SystemId van de bijlage, in kleine letters en zonder accolades. Dit geeft u mee aan Attachment.
fileNamestringDe naam zoals hij is opgeslagen.
extensionstringKleine letters, zonder punt.
contentTypestringHet MIME-type dat CustomerGate uit de extensie heeft afgeleid.
sizenumberBytes.
contentHashstringSHA-256-hash van de inhoud, 64 hexadecimale tekens in kleine letters.
uploadedAtstringTijdstempel.
originstringPortalUser of InsideSales.

De inhoud staat nooit in deze lijst: tien tekeningen kunnen samen al honderd megabyte beslaan. Haal de bestanden één voor één op met Attachment.

Attachment

voaPortal_Attachment(token, attachmentId) geeft de inhoud van één bestand terug, base64-gecodeerd, in dezelfde vorm als Document. attachmentId is de id uit MyAttachments, niet de attachmentId die AddAttachment heeft teruggegeven. Dat is met opzet: een regelnummer is 10000, 20000, 30000 en dus te raden, en een aanvraagnummer ernaast ook. De eigendomscontrole vindt hoe dan ook plaats, dus raden levert niets op; met een sleutel die niet te raden is, begint een poging niet eens.

SleutelTypeToelichting
fileNamestring
contentTypestring
sizenumberBytes.
contentHashstringSHA-256-hash, zodat u kunt controleren wat u hebt ontvangen.
base64stringHet bestand.

Het antwoord is "" bij een ongeldige sessie, een id die geen GUID is, een onbekende id, een bijlage van een andere klant, of een bijlage zonder inhoud: allemaal hetzelfde lege antwoord.

Fouten bij het uploaden

AddAttachment heeft eigen fouten, allemaal HTTP 400 met de melding in de body. Er wordt niets opgeslagen. MyAttachments en Attachment geven nooit een fout: ze antwoorden "".

VoorwaardeDe melding begint met
Bijlagen staan uitAttachments are switched off.
requestNo hoort bij geen enkele aanvraagThere is no web request …
De aanvraag heeft de status Omgezet, Afgewezen of SpamRequest … no longer accepts files: its status is …
De aanvraag is geanonimiseerdRequest … has been anonymized and no longer accepts files.
fileName is leegA file without a name cannot be accepted.
fileName bevat nog een map of een dubbele puntThe file name … is not a file name…
fileName heeft geen extensie, of een van meer dan 10 tekensThe file … has no extension…
Een deel van de naam staat op de weigerlijstFiles of type … are never accepted…
De extensie staat niet in Toegestane soorten bijlagenFiles of type … are not accepted. The allowed types are: …
Groter dan Maximale grootte bijlage (KB)The file … is larger than the maximum of … KB.
contentBase64 is geen geldige base64The content of … is not valid base64.
Lege inhoudThe file … is empty.
De eerste bytes passen niet bij de extensieThe content of … is not a … file.
De aanvraag heeft al het maximale aantal bestandenRequest … already has … files, and that is the maximum.

voaPortal: het klantenportaal

Elke actie is POST .../ODataV4/voaPortal_{Procedure}?company={company id}. Alleen deze vierentwintig procedures zijn aanroepbaar; elke andere procedure in de codeunit is lokaal.

ProcedureParametersGeeft terugRolWijzigt gegevens
RequestCodeemail, ipHashbooleaniedereenmaakt een code, verstuurt een mail
SignInemail, code, ipHashstring: token of ""iedereengebruikt de code, maakt een sessie
ReadSessiontokenJSON-string of ""beideverlengt de sessie
SignOuttokenboolean (altijd true)beidetrekt de sessie in
MyDetailstokenJSON-string of ""beideverlengt de sessie
MyDeliveryAddressestokenJSON-string of ""Inkoperverlengt de sessie
LinkRequesttoken, requestIdbooleanInkoperkoppelt een aanvraag aan de klant
MyRequeststokenJSON-string of ""beideverlengt de sessie
RequestLinestoken, requestNoJSON-string of ""beideverlengt de sessie
MyQuotestokenJSON-string of ""beideverlengt de sessie
MyOrderstokenJSON-string of ""beideverlengt de sessie
MyShipmentstokenJSON-string of ""beideverlengt de sessie
DocumentLinestoken, documentType, documentNoJSON-string of ""beideverlengt de sessie
AcceptQuotetoken, quoteNo, ipHashJSON-stringInkoperlegt een akkoord vast
AcceptQuoteWithDetailstoken, quoteNo, ipHash, poNumber, requestedDeliveryDateJSON-stringInkoperlegt een akkoord vast met twee extra waarden
RejectQuotetoken, quoteNo, ipHash, reasonCode, remarkJSON-stringInkoperlegt een afwijzing vast
MyDocumentstokenJSON-string of ""beideverlengt de sessie
Documenttoken, documentType, documentNoJSON-string of ""beideverlengt de sessie
MyAttachmentstoken, requestNoJSON-string of ""beideverlengt de sessie
Attachmenttoken, attachmentIdJSON-string of ""beideverlengt de sessie
SwitchCustomertoken, customerNo, ipHashJSON-stringbeidezet de sessie over naar een andere klant van hetzelfde adres
MyNotificationSettingstokenJSON-string of ""beideverlengt de sessie
SetNotificationSettingstoken, settingsJSON-string of ""beidewijzigt de eigen berichtkeuzes van de aanroeper
InviteColleaguetoken, email, name, role, privacyConsentVersionJSON-stringInkopermaakt een accountaanvraag voor een collega

Harde grenzen

LijstMaximum
MyRequests200 aanvragen
MyQuotes, MyOrders, MyShipmentselk 100 rijen, actueel en gearchiveerd samen; truncated zegt of er meer waren
MyDeliveryAddresses100 adressen
DocumentLines500 regels
RequestLines500 regels
MyDocuments, archief van Steel 365200 documenten, uit hooguit 1.000 bekeken archiefrijen
MyDocuments, uit rapporten100 per documentsoort
MyAttachmentszoveel bestanden als de aanvraag heeft; Maximum aantal bijlagen per aanvraag begrenst dat tot 100
ReadSession, MyDetailscustomers heeft geen vast maximum; in de praktijk zijn het er één of twee

RequestCode, SignIn, SignOut

RequestCode geeft altijd true terug, na minstens 1,5 seconde; roep hem niet automatisch opnieuw aan, want elke aanroep kan een mail versturen. SignIn geeft het sessietoken van 64 tekens terug, of "" als de code verkeerd, verlopen of al gebruikt is, of als de portaalgebruiker of de klant niet meer is toegelaten; het antwoord zegt nooit welk geval het is. Een juiste code voor een gebruiker die intussen is gedeactiveerd, wordt opgebruikt en geeft "" terug. SignOut trekt de sessie in als het token bestaat en geeft altijd true terug, ook bij een onbekend token; verwijder de cookie hoe dan ook.

ReadSession

Wie er achter het token zit. Roep deze procedure aan bij elke paginaweergave van het portaal: ze controleert de sessie, de portaalgebruiker en de klant, en verlengt de sessie.

SleutelTypeToelichting
emailstringHet adres van de portaalgebruiker.
customerNostringDe klant van deze sessie. Business Central geeft die door aan de website, nooit andersom.
customerNamestringKlantnaam, of "".
rolestringBuyer of Viewer.
weightTypestringHet gewichtstype uit Steel 365 van de klant (Theoretical, Trade, GermanTrade), of "" zonder Steel 365 of als het niet is ingevuld.
expiresAtstringTijdstip waarop de sessie verloopt als hij niet meer wordt gebruikt.
customersarrayElke klant waarvoor dit adres mag optreden, elk als {"no": …, "name": …, "role": …}. Bij een geldige sessie altijd aanwezig en nooit leeg; in het gewone geval één element. Ingetrokken koppelingen en klanten die niet tot het portaal zijn toegelaten, worden weggelaten, dus naar alles in deze lijst kan worden gewisseld.
{
  "email": "buyer@example.com",
  "customerNo": "C00010",
  "customerName": "Example Ltd",
  "role": "Buyer",
  "weightType": "",
  "expiresAt": "2026-09-19T22:30:00.000Z",
  "customers": [
    {"no": "C00010", "name": "Example Ltd", "role": "Buyer"},
    {"no": "C00044", "name": "Example Holding", "role": "Viewer"}
  ]
}

MyDetails

De bedrijfsgegevens van de klant van de sessie, om het offerteaanvraagformulier vooraf in te vullen. Elk veld is een tekenreeks en kan "" zijn. Het klantnummer zit er bewust niet in, en er zijn geen prijzen, kortingen of kredietgegevens. De sleutels zijn company, vatRegistrationNo, address, address2, postCode, city, country, phoneNo, email (het adres van de portaalgebruiker, niet dat op de Klantenkaart) en role. Daarnaast bevat het antwoord customers, dezelfde array als in ReadSession, zodat een formulier kan tonen namens wie iemand de aanvraag doet. Die array zegt welke klanten gekozen mogen worden, niet welke actief is: dat is customerNo in ReadSession.

MyDeliveryAddresses

De geregistreerde leveradressen van de klant van de sessie, alleen voor een Inkoper: {"addresses": [{"code", "name", "address", "address2", "postCode", "city", "country"}]}, allemaal tekenreeksen. Een Meekijker krijgt "", net als bij een ongeldige sessie. Het factuuradres staat niet in deze lijst, maar in MyDetails. Stuur de gekozen code als shipToCode mee in CreateQuoteRequest.

LinkRequest

Zet de klant van de sessie op een offerteaanvraag die net is verstuurd; roep deze procedure dus meteen na CreateQuoteRequest aan, met dezelfde requestId. Ze geeft alleen true terug als de sessie geldig is, de rol Inkoper is, requestId (getrimd, in hoofdletters, hooguit 64 tekens) niet leeg is, en er een offerteaanvraag met dat Aanvraag-id bestaat die nog geen klant heeft. Daarna zet ze de klant op de aanvraag, behoudt ze shipToCode alleen als dat een leveradres van die klant is en wist ze de code anders (de adrestekst blijft staan), en zet ze de Bron van de aanvraag op portal. Een false is geen fout: de aanvraag bestaat hoe dan ook, en de binnendienst neemt haar in behandeling zoals elke andere.

MyRequests

De offerteaanvragen die aan de klant van de sessie zijn gekoppeld, de nieuwste eerst, hooguit 200. Geanonimiseerde aanvragen en aanvragen die als spam zijn gemarkeerd, blijven erbuiten.

SleutelTypeToelichting
nostringAanvraagnummer.
receivedAtstringTijdstempel.
statusstringNew, InProgress, Converted, Rejected, of "" voor een status die deze versie niet kent.
quoteNostringOptioneel. Het verkoopdocument dat uit de aanvraag is gemaakt; bij een order via medewerker een ordernummer.
documentTypestringAanwezig bij quoteNo. quote, of order bij een order via medewerker. Geeft aan naar welke lijst quoteNo moet linken.
quoteNoStatusstringAanwezig bij quoteNo. active bij een actueel document van deze klant, completed bij een vrijgegeven offerte in het offertearchief of een gearchiveerde order, anders "".
linesnumberAantal aanvraagregels.
projectNamestringSinds 1.0.44. Het project dat de aanvrager noemde (projectName van CreateQuoteRequest), of "". Altijd aanwezig.

RequestLines

De regels van een van uw eigen offerteaanvragen, in de vorm waarin CreateQuoteRequest ze weer accepteert. Hierop is "nog eens hetzelfde bestellen" gebouwd: de website leest de regels van de aanvraag van vorige maand en vult er een nieuw formulier mee. Het eigendom wordt op de aanvraag gecontroleerd, met precies dezelfde drie voorwaarden als bij MyAttachments. Een omgezette aanvraag zit er met opzet bij: juist de aanvraag die vorige maand een order werd, wil iemand herhalen, dus er is geen filter op status behalve spam.

Het antwoord is {"requestNo": …, "lines": [ … ]}, met de regels in volgorde van regelnummer, hooguit 500. Elk element heeft lineNo (ter informatie, stuur het niet terug) en verder de sleutels die CreateQuoteRequest verwacht: itemNo, variantCode, profileCode, qualityCode, description, quantity, unitOfMeasureCode, lengthMm, widthMm, thicknessMm, lineRemarks en sinds 1.0.44 inspectionCertificate (None, Certificate22, Certificate31 of Certificate32) en processing (een array met de waardenamen in de volgorde van de enum, [] als er geen zijn). Sinds 1.0.44 bevat het antwoord ook projectName, het project van de aanvraag of ""; een herhaalde bestelling is vaak voor een andere bouwplaats, dus bied het aan als suggestie en niet als vaststaand gegeven. Een eigen aanvraag zonder regels geeft {"requestNo": "WEB000123", "lines": []}.

MyQuotes

De verkoopoffertes van de klant van de sessie: eerst de actuele offertes, het hoogste nummer eerst, daarna offertes die alleen nog in het offertearchief bestaan (omgezet of verwijderd), alleen vrijgegeven versies, per nummer de hoogste gearchiveerde versie. Samen hooguit 100 rijen. Het antwoord is {"quotes": [ … ], "truncated": <Boolean>}.

SleutelTypeToelichting
nostringOffertenummer.
documentDatestringDatum.
validUntilstringDe geldigheidsdatum van de offerte, anders de vervaldatum uit Steel 365, anders "".
yourReferencestringDe referentie van de klant: Extern documentnr.
requestNostringDe webaanvraag waaruit deze offerte is gemaakt, of "". Business Central geeft alleen een nummer terug van een bestaande aanvraag van deze offerte of deze klant; vrije tekst in Uw referentie wordt nooit doorgegeven. Ga niet uit van een vast nummerpatroon: de nummerreeks is instelbaar.
statusstringOpen, Released, of "" bij elke andere status.
linesnumberAantal artikelregels.
totalWeightKgnumberOptioneel, alleen met Steel 365. Totaalgewicht in kg.
hasDocumentbooleantrue alleen als Document deze offerte kan afdrukken. Bij gearchiveerde rijen altijd false.
acceptedbooleanEr is via het portaal een akkoord vastgelegd.
acceptedAtstringSinds 1.0.44. Het moment van dat akkoord, hetzelfde tijdstempel dat AcceptQuote teruggaf, of "".
rejectedAtstringSinds 1.0.44. Het moment waarop de klant de offerte met RejectQuote afwees, hetzelfde tijdstempel dat die aanroep teruggaf, of "".
rejectionReasonstringSinds 1.0.44. De reden die bij die afwijzing is opgegeven: TooExpensive, DeliveryTooLate, OrderedElsewhere, NoLongerNeeded, Other, of NotStated als de website een reden stuurde die de app niet kent. "" zonder afwijzing. De eigen opmerking van de klant komt niet terug; die is voor de binnendienst.
commentstringAlleen bij actuele rijen: de opmerking die de binnendienst voor de klant heeft geschreven. Nooit de interne werkbeschrijving.
orderNo, orderNoStatusstringBij gearchiveerde rijen, optioneel: de order waarin deze offerte is omgezet, uit het documentarchief van Steel 365, met active, completed of "". Link alleen naar de order als dit niet "" is.

Regels voor het lezen van het antwoord van de klant (sinds 1.0.44):

  • Lees deze sleutels na elke AcceptQuote en RejectQuote en bouw de knoppen erop: geen knoppen als accepted true is of rejectedAt niet "" is. Vóór 1.0.44 toonde een website die na een afwijzing ververste beide knoppen weer, en een tweede afwijzing gaf already.
  • De vier sleutels zijn er altijd, gevuld of "" of false. Een gearchiveerde offerte heeft ze ook: een offerte die is afgewezen en daarna is omgezet of verwijderd, toont de afwijzing nog steeds.
  • Beide kunnen gevuld zijn. RejectQuote weigert een offerte die al geaccepteerd is, maar AcceptQuote weigert geen offerte die is afgewezen: een klant die nee zei en van gedachten verandert, kan nog accepteren. Zijn beide gevuld, dan is het latere tijdstempel het laatste woord van de klant.
  • De tijdstempels zijn ISO 8601 in UTC, zoals elk tijdstempel hier; vergelijk ze als momenten, niet als tekst.

MyOrders

De verkooporders van de klant van de sessie: actuele orders, het hoogste nummer eerst, daarna orders die alleen nog in het orderarchief bestaan, per nummer de hoogste gearchiveerde versie, met elke status. Samen hooguit 100 rijen, als {"orders": [ … ], "truncated": <Boolean>}.

SleutelTypeToelichting
nostringOrdernummer.
quoteNo, quoteNoStatusstringDe offerte waaruit de order is voortgekomen, met active, completed of "". Bij gearchiveerde rijen komt die uit het documentarchief van Steel 365.
orderDate, requestedDeliveryDate, promisedDeliveryDatestringDatums uit de kop van de order. Per regel staan ze in DocumentLines.
yourReferencestringDe referentie van de klant (Extern documentnr.).
statusstringOpen, Released of "".
completelyShippedbooleanActueel: het eigen veld van de order. Gearchiveerd: per regel herrekend uit geboekte verzendregels.
linesnumberAantal artikelregels.
hasDocumentbooleanZoals bij MyQuotes.
commentstringAlleen bij actuele rijen: de opmerking voor de klant.

MyShipments

De geboekte verkoopverzendingen van de klant van de sessie, de nieuwste eerst, hooguit 100, als {"shipments": [ … ], "truncated": <Boolean>}.

SleutelTypeToelichting
nostringVerzendnummer.
deliveryDatestringBoekingsdatum van de verzending.
orderNo, orderNoStatusstringDe order waar de verzending bij hoort, met active, completed of "".
yourReferencestringDe referentie van de klant (Extern documentnr.).
shipToAddressobject{"name", "postCode", "city"}. Straat en huisnummer worden niet meegegeven.
linesnumberAantal artikelregels.
hasDocumentbooleanZoals bij MyQuotes.
packageTrackingNostringHet pakketnummer van de vervoerder, of "".
shippingAgentCodestringDe code van de expediteur op de verzending, of "".
trackingUrlstringEen kant-en-klare link naar de track-and-tracepagina van de vervoerder, of "".

Die drie sleutels zijn altijd aanwezig en vaak leeg: een staalhandel levert het meeste met eigen vrachtwagens, en dan is er geen vervoerder en geen pakketnummer. Lees "" als "geen track-and-trace voor deze verzending" en toon geen knop. Business Central stelt trackingUrl samen uit het internetadres op de kaart van de expediteur, dat een plaatshouder voor het pakketnummer bevat, met dezelfde hulpfunctie die de standaardactie in Business Central zelf gebruikt. Het veld is leeg als er geen expediteurcode is, de expediteur niet meer bestaat, de expediteur geen internetadres heeft of er geen pakketnummer is: een link naar een zoekpagina van een vervoerder waarop niets is ingevuld, stelt alleen maar teleur. Laat het sjabloon het schema weg, dan wordt dat https://. CustomerGate geeft het sjabloon zelf nooit prijs, alleen de kant-en-klare link.

DocumentLines

Parameters: documentType is quote, order of shipment (niet hoofdlettergevoelig; elke andere waarde geeft ""), en documentNo is het documentnummer. Het eigendom wordt gecontroleerd voordat er ook maar één regel wordt gelezen. Bij quote en order wordt eerst het actuele document geprobeerd; bestaat dat niet, dan de hoogste versie in het archief, bij offertes alleen vrijgegeven versies. Alleen regels van het type Artikel komen terug, hooguit 500; tekst- en opmerkingsregels worden weggelaten. Het antwoord is {"lines": [ … ]}.

SleutelTypeWanneerToelichting
lineNonumberaltijdRegelnr.
itemNostringaltijdArtikelnummer.
descriptionstringaltijd
formatstringaltijdVariantcode: de afmeting.
quantitynumberzonder Steel 365Aantal.
unitOfMeasureCodestringzonder Steel 365Eenheid van quantity.
outstandingQuantity, quantityShippednumberzonder Steel 365, actuele orderregels
profile, grade, formatDescriptionstringSteel 365, optioneelItem Profile Code DMX, Quality Code DMX, Item Variant Description DMX.
unitstringSteel 365, optioneelDoc. Unit of Measure Code DMX, de onvertaalde optienaam. Geeft aan welk van de drie aantallen leidend is; de referentiewebsite verwacht PCS, KG of MTR.
quantityPieces, quantityMeters, quantityKgnumberSteel 365Quantity Pieces DMX, Quantity Length DMX, Quantity Weight DMX.
weightTypestringSteel 365, optioneelTheoretical, Trade of GermanTrade.
lengthMm, widthMm, thicknessMmnumberSteel 365, optioneelSinds 1.0.44. Length DMX, Width DMX en Thickness DMX van de artikelvariant van de regel (itemNo + format), in millimeters zoals DXSteel ze opslaat: dezelfde getallen die voaVariants teruggeeft als DXLength, DXWidth en DXThickness.
customFormatbooleanSteel 365, optioneelSinds 1.0.44. true als de variant een eenmalige afmeting is (Format Type DMX is OneTime): een maat die DXSteel voor deze order maakte, bijvoorbeeld een balk op 9000 mm gezaagd. false bij een handelsafmeting of een niet-handelsafmeting.
outstandingPieces, outstandingMeters, outstandingKg, shippedPieces, shippedMeters, shippedKgnumberSteel 365, actuele orderregelsOpenstaande en verzonden aantallen.
requestedDeliveryDatestringactuele orderregelsWat de klant voor deze regel heeft gevraagd. "" als het niet is ingevuld.
promisedDeliveryDatestringactuele orderregelsWat er voor deze regel is toegezegd. "" als het niet is ingevuld.
lineStatusstringactuele orderregelsHoe ver deze regel is: Open, PartlyShipped, Shipped of Invoiced.

Aandachtspunten bij het lezen van regels:

  • Tekstsleutels van Steel 365 worden weggelaten als ze leeg zijn, terwijl getalsleutels van Steel 365 aanwezig zijn zodra het veld bestaat; 0 betekent dus nul en een ontbrekende sleutel betekent onbekend. Toon dan een streepje, geen 0.
  • Met Steel 365 zijn stuks, meters en kilo's drie aparte aantallen: tel ze nooit op en kies er niet zelf een; unit zegt welk aantal leidend is.
  • Openstaande en verzonden waarden bestaan alleen bij actuele orderregels; reken ze niet zelf uit.
  • requestedDeliveryDate, promisedDeliveryDate en lineStatus bestaan om dezelfde reden alleen bij actuele orderregels: op een offerteregel is niets besteld, dus elke uitspraak over levering zou over iets gaan dat nog niet bestaat.
  • De twee datums komen van de regel en niet van de kop. Dat is juist de bedoeling: bij een deellevering schuift de ene regel wel en de andere niet, en de kop vertelt dan het gemiddelde van twee verhalen. MyOrders blijft voor het lijstoverzicht de datums van de kop geven.
  • lineStatus is afgeleid, en wel uit de standaardaantallen (Aantal, Verzonden aantal, Gefactureerd aantal) die Business Central op elke verkoopregel bijhoudt, met of zonder Steel 365. Het betekent dus in beide soorten omgevingen hetzelfde, terwijl de aantallen ernaast dat niet doen. De controles gaan van verst naar minst ver: gefactureerd gaat voor verzonden, verzonden voor deels verzonden. Een regel met een aantal van nul is altijd Open en nooit Shipped.
  • Er komt nooit een prijs, korting, marge of bedrag terug.
  • De maat van een regel (lengthMm, widthMm, thicknessMm, customFormat, sinds 1.0.44) komt van de artikelvariant van de regel, bij elke soort regel: offerte, order of zending, actueel of gearchiveerd. De vier sleutels volgen de regel voor Steel 365 hierboven: aanwezig zodra DXSteel het veld heeft, dus 0 betekent nul (een plaat heeft een breedte, een balk niet) en een ontbrekende sleutel betekent onbekend. Ze ontbreken ook als de regel geen variantcode heeft of de variant niet meer bestaat. Zonder Steel 365 zijn ze er nooit.
{
  "lines": [
    {"lineNo": 10000, "itemNo": "1000", "description": "HEA 200 S235JR", "format": "12000",
     "profile": "HEA200", "grade": "S235JR", "unit": "PCS", "quantityPieces": 4,
     "quantityMeters": 48, "quantityKg": 2035.2, "weightType": "Theoretical",
     "lengthMm": 12000, "widthMm": 0, "thicknessMm": 0, "customFormat": false,
     "requestedDeliveryDate": "2026-11-03", "promisedDeliveryDate": "2026-11-05",
     "lineStatus": "PartlyShipped"}
  ]
}

Een regel opnieuw bestellen

Sinds 1.0.44. Om een regel van een eerdere offerte of order in een nieuwe CreateQuoteRequest te zetten, stuurt u:

Regelsleutel van `CreateQuoteRequest`Uit `DocumentLines`
itemNoitemNo
variantCodeformat, alleen als customFormat false is (een handelsafmeting of een niet-handelsafmeting uit de catalogus). Laat de sleutel weg bij een eenmalige afmeting.
lengthMm, widthMm, thicknessMmlengthMm, widthMm, thicknessMm, als customFormat true is. Stuur ze zoals ze binnenkwamen; een 0 wordt overgeslagen.
profileCode, qualityCodeprofile, grade
descriptiondescription
quantity, unitOfMeasureCodeHet leidende aantal: unit zegt welk van quantityPieces, quantityKg en quantityMeters dat is. Stuur het met de eenheidscode die uw formulier voor die eenheid gebruikt, net als bij een nieuwe aanvraag. Zonder Steel 365: quantity en unitOfMeasureCode.

Waarom niet de variantcode van een eenmalige afmeting: dat is een variant die DXSteel voor één order maakte, geen maat uit de catalogus. Die is niet bedoeld om opnieuw aan te bieden, en kan worden geblokkeerd of opgeruimd. Met het artikelnummer en de afmetingen en zonder variantCode doet de omzetting wat een verkoper doet die de maat in de offerteregel typt: DXSteel zoekt eerst een bestaande variant met precies deze afmetingen en maakt pas een nieuwe eenmalige afmeting als die er niet is. Die weg wordt geaccepteerd bij binnenkomst, waar de controle op de catalogus naar het artikel kijkt en niet naar de variant, en bij het omzetten, waar een artikel met meerdere maten zonder variantcode alleen wordt geweigerd als er ook geen afmeting is opgegeven.

Een regel van een eerdere aanvraag komt precies in deze vorm terug uit RequestLines; gebruik die als u het aanvraagnummer hebt.

AcceptQuote

Legt vast dat de klant een vrijgegeven verkoopofferte accepteert. Alleen voor een Inkoper. Er wordt in Business Central niets omgezet of vrijgegeven: het akkoord is een signaal voor de binnendienst, die de offerte met de hand omzet. De procedure geeft altijd een object terug met outcome (recorded, already, expired of refused), acceptedAt (een tijdstempel, of "") en, alleen bij refused of expired, errorCode. De controles, op volgorde:

ControleUitkomst
Sessie niet geldigrefused
De rol is geen Inkoperrefused
quoteNo is leeg of langer dan 20 tekensrefused
Geen actuele offerte met dat nummer voor deze klantrefused
De status van de offerte is niet Vrijgegevenrefused
Er bestaat al een akkoord voor deze offertealready, met de oorspronkelijke acceptedAt
De geldigheidsdatum is ingevuld en ligt vóór vandaagexpired
Een andere aanroep legt op hetzelfde moment een akkoord voor deze offerte vastalready, acceptedAt = ""
Andersrecorded, acceptedAt = nu

refused zegt nooit waarom, en already is geen fout: toon dan dat het akkoord binnen is. Probeer deze aanroep niet automatisch opnieuw. Bij het akkoord worden het offertenummer, de klant, het e-mailadres van de portaalgebruiker, het moment, de IP-hash, de documentdatum, het bedrag inclusief btw, de valuta, het aantal regels, de laatste archiefversie en een vingerafdruk van de regels bewaard, zodat duidelijk blijft waarmee is ingestemd als de offerte later verandert. De mailtaak stuurt daarna de klant een bevestiging en de binnendienst een melding.

AcceptQuoteWithDetails

Hetzelfde akkoord als AcceptQuote, met de twee dingen die een klant erbij wil zeggen: zijn inkoopordernummer en de datum waarop hij het staal wil. Elke controle, elke uitkomst en elke sleutel van het antwoord is gelijk; alleen de twee parameters zijn nieuw.

Waarom een tweede procedure en niet twee extra parameters. OData koppelt parameters op naam, dus een aanroep van voaPortal_AcceptQuote die de nieuwe namen niet meestuurt, zou een kale 400 opleveren. Een parameter toevoegen aan een gepubliceerde actie breekt daarom bestaande aanroepen. AcceptQuote behoudt zijn drie parameters, blijft werken en roept deze procedure aan met twee lege waarden.

ParameterToelichting
poNumberHet inkoopordernummer van de klant, hooguit 35 tekens. Een langere waarde wordt afgekapt.
requestedDeliveryDateYYYY-MM-DD. Een waarde die geen datum is, laat de aanroep niet mislukken: het akkoord is het belangrijkste en mag niet verloren gaan door een typefout in een veld ernaast. De datum blijft dan leeg, net alsof de parameter was weggelaten. Dit wijkt met opzet af van CreateQuoteRequest, waar dezelfde waarde de aanroep wel laat mislukken, omdat de datum daar deel uitmaakt van wat er wordt gevraagd.

Waar de twee waarden terechtkomen: op het akkoord, dat de binnendienst ziet op Akkoorden op offertes via de website. Deze aanroep schrijft ze niet naar de verkoopofferte, want het portaal leest verkoopdocumenten en schrijft er nooit in, en daardoor blijft dit koppelvlak klein. Ze bereiken het verkoopdocument langs twee wegen, die allebei door een persoon in Business Central worden uitgevoerd: automatisch als de binnendienst de offerte omzet in een order, waarbij het inkoopordernummer in Uw referentie en Extern documentnr. belandt en de datum in Verzochte leverdatum, of met de actie Op verkoopdocument zetten op de lijst met akkoorden. Geen van beide wegen overschrijft ooit een waarde die al op het document staat: wat de binnendienst heeft ingetypt, gaat voor op wat een week eerder op een website is gekozen.

RejectQuote

Legt met een reden vast dat de klant niet verdergaat met een vrijgegeven offerte. Alleen voor een Inkoper, met hetzelfde klantslot, dezelfde eis dat de offerte is vrijgegeven, en dezelfde uniforme weigering als AcceptQuote. De offerte wordt niet geannuleerd: er verandert niets in Business Central, de offerte blijft precies zoals ze was, en de binnendienst krijgt een regel in de werklijst dat de klant nee heeft gezegd. Wie "te duur" aanklikt, wil meestal een gesprek, en een portaal dat de offerte sluit, neemt dat gesprek weg.

ParameterToelichting
reasonCodeEen van TooExpensive, DeliveryTooLate, OrderedElsewhere, NoLongerNeeded, Other. Elke andere waarde, ook een lege tekenreeks, wordt opgeslagen als NotStated en laat de aanroep niet mislukken: de afwijzing zelf is de boodschap. Toon de lijst in uw eigen taal en stuur de vaste waarde mee.
remarkVrije tekst, optioneel, hooguit 250 tekens. Het is een persoonsgegeven en wordt na de bewaartermijn gewist; de reden en de offerte blijven staan.

Het antwoord heeft outcome (recorded, already of refused), rejectedAt (een tijdstempel, of "") en, alleen bij refused, errorCode, dat altijd NotAuthorized is. Een ongeldige sessie, een andere rol, een leeg of te lang quoteNo, geen actuele vrijgegeven offerte van deze klant en een offerte waarvoor al een akkoord bestaat, geven allemaal refused: een akkoord intrekken is een gesprek, geen klik. Bestaat er al een afwijzing, dan is het antwoord already met de oorspronkelijke rejectedAt. Er is geen expired: een offerte afwijzen waarvan de geldigheidsdatum is verstreken, mag wel, en dat is juist het moment waarop een klant dat laat weten.

SwitchCustomer

Zet deze sessie over naar een andere klant waaraan het aangemelde adres is gekoppeld. Lees eerst Eén adres bij meerdere klanten hierboven: dit is de enige procedure in het portaal die een klantnummer accepteert. customerNo wordt getrimd en omgezet naar hoofdletters; een waarde langer dan 20 tekens wordt geweigerd, niet afgekapt. ipHash komt op de logregel van elke poging.

SleutelTypeToelichting
outcomestringswitched of refused.
customerNostringDe klant waarop de sessie na de aanroep staat. "" bij refused: een weigering noemt nooit een klant.
errorCodestringAlleen bij refused. Altijd NotAuthorized.
ControleUitkomst
Sessie niet geldigrefused, en er wordt niets gelogd: zonder sessie is er niemand om te loggen
customerNo is leeg of langer dan 20 tekensrefused
Sessie staat al op deze klantswitched, er wordt niets geschreven en niets gelogd
Geen koppeling tussen dit adres en die klantrefused
De koppeling bestaat wel, maar is ingetrokken of geanonimiseerdrefused
De klant is niet tot het portaal toegelatenrefused
Andersswitched; de klant en de rol van de sessie komen uit de koppeling

Roep na een wissel ReadSession opnieuw aan, of gebruik de customerNo uit het antwoord: alles wat het portaal vanaf dat moment teruggeeft, is op de nieuwe klant gefilterd, inclusief de rol.

MyNotificationSettings

Waarover deze portaalgebruiker per e-mail bericht wil krijgen. Alles staat uit tot de gebruiker het zelf aanzet, en alleen dit endpoint kan het aanzetten: de binnendienst kan de vlaggen wel lezen maar niet zetten, en er is geen bulkactie.

SleutelTypeToelichting
enabledbooleanDe hoofdschakelaar van het bedrijf (Portaalberichten ingeschakeld). Uit betekent dat er niets in de wachtrij komt en niets wordt verstuurd, wat de vijf hieronder ook zeggen. De waarde staat erbij zodat uw scherm kan uitleggen waarom er niets aankomt; een website kan haar niet wijzigen.
quoteReleasedbooleanEr is een offerte voor deze klant vrijgegeven.
orderConfirmedbooleanEr is een order voor deze klant vrijgegeven.
shipmentPostedbooleanEr is een verzending voor deze klant geboekt.
invoicePostedbooleanEr is een factuur voor deze klant geboekt.
requestReceivedbooleanEr is een offerteaanvraag van deze klant in het portaal binnengekomen.

De vijf gelden per portaalgebruiker en per klant, want ze horen bij de koppeling. Wie voor twee bedrijven inkoopt, wil misschien verzendmail voor het ene en niets voor het andere. Elke mail vermeldt waarom hij is verstuurd en waar hij uit te zetten is, met een link naar het portaal, waar de ontvanger zich gewoon aanmeldt. Er is met opzet geen afmeldlink die zonder aanmelden werkt: zo'n link werkt voor iedereen die de e-mail in handen heeft, en dat is precies het soort endpoint dat deze app niet heeft.

SetNotificationSettings

Wijzigt de eigen keuzes van de aanroeper en geeft de nieuwe stand terug, in dezelfde vorm als MyNotificationSettings. De parameter settings is een JSON-object als tekenreeks, met de sleutels van MyNotificationSettings, bijvoorbeeld {"invoicePosted":true}.

  • Alleen de sleutels die u stuurt, veranderen. Wat u weglaat, houdt zijn waarde, dus een scherm dat één vinkje omzet, hoeft de andere vier niet mee te sturen en kan een keuze uit een ander tabblad niet ongedaan maken.
  • enabled wordt genegeerd als u het meestuurt. Het is de schakelaar van het bedrijf; het staat in het antwoord zodat u iets kunt uitleggen, niet om het in te stellen.
  • Een waarde die geen true of false is, laat die sleutel ongemoeid.
  • Een settings die geen geldige JSON is, wijzigt niets en geeft de huidige stand terug. Er is niets stuk: de klant klikte, er gebeurde niets, en het scherm toont wat er werkelijk staat.
  • Uitzetten werkt meteen, ook voor berichten die al in de wachtrij staan: op het moment van versturen wordt de wachtrij opnieuw aan deze vlaggen getoetst.

InviteColleague

Een Inkoper vraagt portaaltoegang aan voor een collega bij dezelfde klant. Dit geeft geen toegang: het maakt een gewone accountaanvraag waarin de klant al is ingevuld, en die keurt de binnendienst met de hand goed in Business Central, met dezelfde bevestiging als bij elke andere. Een inkoper die zijn eigen collega's binnen zou kunnen laten, zou toegang tot bedrijfsgegevens uitdelen, en dat is nu juist de enige handeling die deze app aan een mens overlaat.

ParameterToelichting
emailHet adres van de collega. Genormaliseerd zoals elk adres hier; er moet een @ in staan.
nameDe naam van de collega, hooguit 100 tekens.
roleBuyer of Viewer, als voorstel. Elke andere waarde wordt Viewer. Wie goedkeurt, kiest de rol.
privacyConsentVersionVerplicht. De versie van de privacyverklaring die de uitnodiger op het uitnodigingsformulier heeft bevestigd. Deze aanroep legt het adres van een derde vast, en de uitnodiger staat daarvoor in; de waarde komt op de aanvraag te staan, naast het adres van de uitnodiger. Zonder deze waarde wordt de aanroep geweigerd.

Het antwoord heeft outcome (created, already, throttled of refused), number (het nummer van de accountaanvraag bij created, anders "") en, alleen bij refused, errorCode, dat altijd NotAuthorized is. Een ongeldige sessie, een andere rol, Maximum aantal uitnodigingen per dag op 0, een email zonder @ en een lege privacyConsentVersion geven allemaal refused. Een adres dat bij deze klant al een actieve portaalgebruiker heeft, geeft already; een uitnodiger die vandaag zijn maximum heeft bereikt, krijgt throttled.

already en throttled staan met opzet apart, anders dan de uniforme weigeringen elders. Er valt hier niets te lekken: de aanroeper is aangemeld, heeft het adres zelf ingetypt, en already zegt alleen iets over de portaalgebruikers van zijn eigen klant, voor wie hij toch al mag optreden. Zo kan een scherm melden "deze collega kan zich al aanmelden" in plaats van "het is niet gelukt". Een ingetrokken koppeling geeft geen toegang, dus een collega die vertrok en terugkwam, kan opnieuw worden uitgenodigd. De collega krijgt de gewone ontvangstbevestiging van een accountaanvraag, met één extra regel die zegt wie hem heeft uitgenodigd, want "wij hebben uw aanvraag ontvangen" zegt niets aan iemand die niets heeft aangevraagd. Die mail gaat mee met de volgende mailronde.

MyDocuments

De pdf's die de klant van de sessie kan krijgen, als {"documents": [ … ]}, zonder truncated. De bron is een van twee, nooit beide: het documentarchief van Steel 365 als DXSteel Doc Archive is geïnstalleerd, anders de rapporten als Portaaldocumenten uit rapporten aan staat. Is het archief er niet en staat die instelling uit, dan is de lijst altijd leeg. Gaat er bij het lezen van het archief iets mis, dan krijgt u een lege lijst in plaats van een fout, en de pagina CustomerGate-instellingen toont waarom.

Archiefrijen. Een document staat alleen in de lijst als het aan het nummer van deze klant is gekoppeld, het relatietype Klant is en de rubriek op Archiefrubrieken staat met een andere soort dan Geen. De nieuwste eerst.

SleutelTypeToelichting
idstringSystemId van het archiefrecord, in kleine letters met koppeltekens. De sleutel om te downloaden.
fileNamestringZoals opgeslagen in het archief. Kan spaties en accenten bevatten; maak de naam schoon voordat u hem in een header zet.
contentTypestringapplication/pdf bij .pdf, anders application/octet-stream.
categorystringDe soort: SalesQuote, SalesOrder, SalesInvoice, SalesCreditMemo, Proforma, Waybill of WarehouseShipment. Niet uw eigen rubrieknaam.
documentNostringHet nummer van het brondocument, of "".
documentTypestringquote, order, shipment of "". Pas ingevuld nadat Business Central heeft bevestigd dat documentNo zo'n document van deze klant is. Facturen, creditnota's en proforma's krijgen altijd "".
documentNoStatusstringAlleen als documentType order is: active of completed.
datestringDe aanmaakdatum uit het archief, anders de datum waarop het record is aangemaakt, anders "".
orderNo, orderNoStatus, invoiceNo, creditMemoNostringOptioneel. Nummers van bijbehorende documenten, met active, completed of "" voor de order.

Een archiefdocument downloaden. Business Central kan de bytes niet via deze codeunit teruggeven zonder afhankelijk te worden van de app van iFacto, dus de website haalt ze met de id op bij de API van iFacto:

POST https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/ifacto/dxsteel/v1.0/companies({company id})/documentArchives({id})/Microsoft.NAV.Download
{}

-> {"value": "<base64>"}

Download alleen een id die voorkomt in de lijst van MyDocuments die u net voor diezelfde sessie hebt opgehaald. Neem nooit een id uit de browser over om die ongecontroleerd door te geven: het account van de website heeft leesrecht op het archief nodig om te downloaden, en in hetzelfde archief staan ook inkoopfacturen en interne correspondentie. VOA CG S365 ARCHIVE beperkt die toegang met een beveiligingsfilter tot de toegestane rubrieken, maar de eigendomscontrole is MyDocuments. Een lege value betekent dat er geen bestand bij het archiefrecord zit.

Rapportrijen. Alleen documentsoorten waarvoor een rapport is ingesteld (een documentlay-out op de Klantenkaart, anders Rapportselecties): actuele offertes en orders, geboekte verzendingen, facturen en creditnota's, in die volgorde, elk met het hoogste nummer eerst en hooguit 100 per soort.

SleutelTypeToelichting
idstringSystemId van het document in Business Central. Ter informatie; downloaden doet u met Document.
sourcestringAltijd report. Ontbreekt bij archiefrijen: zo houdt u de twee uit elkaar.
reportTypestringquote, order, shipment, invoice of creditMemo. Geef dit mee als documentType aan Document.
fileNamestring{category} {document number}.pdf, bijvoorbeeld SalesInvoice 103001.pdf.
contentTypestringapplication/pdf.
categorystringSalesQuote, SalesOrder, Waybill (verzendingen), SalesInvoice of SalesCreditMemo.
documentNostringDocumentnummer. Geef dit mee als documentNo aan Document.
documentTypestringquote, order of shipment; "" bij facturen en creditnota's.
documentNoStatusstringAlleen bij orders: active.
datestringOfferte: documentdatum. Order: orderdatum. Verzending, factuur, creditnota: boekingsdatum.
orderNo, orderNoStatus, invoiceNo, creditMemoNostringOptioneel. Nummers van bijbehorende documenten.

Document

De pdf van één document, afgedrukt met het rapport dat Business Central daarvoor gebruikt, en alleen bij de rapportbron: waar het archief van Steel 365 is geïnstalleerd, geeft dit altijd "" en gebruikt u de download uit het archief hierboven. documentType is quote, order, shipment, invoice of creditMemo (niet hoofdlettergevoelig) en documentNo is het nummer. De procedure geeft "" terug bij een ongeldige sessie, een onbekend type, een document dat niet bestaat of niet van deze klant is, een documentsoort zonder rapport, of een lege afdruk. Offertes en orders moeten actueel zijn; gearchiveerde versies kunnen niet worden afgedrukt. In alle andere gevallen komen fileName, contentType en base64 terug: de pdf zelf.

Het rapport wordt uitgevoerd met de rechten van het websiteaccount (tenant-set VOA CG DOCUMENTS). Mislukt het rapport, meestal doordat een klantspecifieke indeling een tabel leest die de set niet dekt, dan mislukt de aanroep met een fout die het object noemt; voeg die tabel toe aan de set. Een download archiveert de offerte of order niet, maar de standaardrapporten verhogen wel het veld Afgedrukt. hasDocument in MyQuotes, MyOrders en MyShipments is precies dan true als de rapportbron actief is en er voor die documentsoort een rapport voor deze klant is ingesteld.

MyAttachments en Attachment

Deze twee horen bij de bijlagen van een offerteaanvraag en zijn hierboven beschreven, onder Bijlagen bij een aanvraag. Ze gedragen zich als elke andere lezende procedure van het portaal: ze verlengen de sessie, staan open voor beide rollen en geven "" terug als er niets voor u is.

Webhooks: Business Central meldt zich bij u

Hierboven stelt steeds de website Business Central een vraag. Dit is de andere richting: Business Central roept een adres van u aan zodra er iets is gebeurd. Zo hoeft een website niet te pollen en niet op de nachtelijke synchronisatie te wachten om te horen dat er vanochtend een verzending de deur uit is gegaan.

Webhooks staan uit tot een beheerder in de Business Central-client een abonnement aanmaakt op de pagina Webhookabonnementen. Er is geen webservice die abonnementen aanmaakt of leest: het adres dat Business Central aanroept en het geheim waarmee het ondertekent, mag een website niet zelf kunnen instellen. Ook moet de beheerder de app in Extensiebeheer toestaan om uitgaande aanroepen te doen; zolang dat niet is gebeurd, komen gebeurtenissen wel in de wachtrij, maar wordt er niets verstuurd.

De gebeurtenissen

Waarde van eventWanneer hij optreedtWaar hij vandaan komt
QuoteReleasedEen verkoopofferte wordt vrijgegevende standaardroutine voor vrijgeven (niet in de voorbeeldstand)
QuoteAcceptedEen klant accepteert een offerte in het portaalAcceptQuote, op het moment dat het akkoord wordt vastgelegd
OrderCreatedEr wordt een verkooporder uit een verkoopofferte gemaaktde standaardomzetting van offerte naar order
ShipmentPostedEr wordt een verkoopverzending geboektde standaardboekingsroutine
InvoicePostedEr wordt een verkoopfactuur geboektde standaardboekingsroutine
AccountApprovedEen accountaanvraag wordt goedgekeurdde werklijst voor accountaanvragen
AccountRejectedEen accountaanvraag wordt afgewezende werklijst voor accountaanvragen
RequestConvertedEen offerteaanvraag wordt een verkoopofferte of een verkooporderde status van de aanvraag, langs welke weg die ook is gewijzigd
RequestRejectedEen offerteaanvraag wordt afgewezende status van de aanvraag, langs welke weg die ook is gewijzigd
PingAlleen via Proefgebeurtenis versturen op de abonnementenlijstnooit vanzelf

Twee bewuste gaten:

  • Een order via medewerker leidt niet tot OrderCreated. Een aanvraag die rechtstreeks een verkooporder wordt, meldt RequestConverted met documentType request en een orderNo. Elke gebeurtenis heeft precies één bron, zodat een ontvanger nooit twee namen voor hetzelfde feit krijgt.
  • Spam is geen gebeurtenis. Een accountaanvraag die als spam is gemarkeerd, leidt tot geen enkele gebeurtenis: er is in het bedrijf niets gebeurd, en een antwoord erop bevestigt dat het adres bestaat.
  • De opruimtaak anonimiseert oude offerteaanvragen die nog op Nieuw of In behandeling stonden, en zet ze daarbij op Afgewezen. Die leiden niet tot RequestRejected; anders kwam er elke nacht een stortvloed aan gebeurtenissen binnen over aanvragen van een jaar geleden.

De aanroep

POST https://yoursite.example/api/bc-webhook
Content-Type: application/json; charset=utf-8
Accept: application/json
X-VOA-Timestamp: 2026-09-20T09:12:33.000Z
X-VOA-Signature: sha256=6687de2bb04779680fe96e12682e4a2b564bc4a3cc329edb2f551056de039e44
X-VOA-Event: InvoicePosted
X-VOA-Event-Id: 5b7a1f0c-2d43-4e18-9a55-0c7e3b6d81f2
X-VOA-Subscription: WEBSITE-ORDERS

{"event":"InvoicePosted", …}
HeaderBetekenis
X-VOA-TimestampWanneer de bezorging is ondertekend, ISO 8601 in UTC. Onderdeel van de handtekening.
X-VOA-Signaturesha256= gevolgd door 64 hexadecimale tekens in kleine letters.
X-VOA-EventDezelfde waarde als event in de body, zodat een router niet eerst hoeft te ontleden.
X-VOA-Event-IdDezelfde waarde als eventId in de body. Gebruik die om dubbele bezorgingen te herkennen.
X-VOA-SubscriptionDe code van het abonnement, zodat één endpoint meerdere abonnementen kan bedienen.

Antwoord met een 2xx-statuscode zodra u de gebeurtenis hebt opgeslagen, en doe het werk pas daarna. Elk ander antwoord dan 2xx telt als mislukt, net als een verbinding die niet binnen 10 seconden antwoordt. Het adres moet zelf antwoorden: bij een omleiding gaan de handtekeningheaders niet mee, dus een 301 naar een andere host komt daar zonder bruikbare handtekening aan.

De payload

De body is één JSON-object. Het bevat id's, statussen, bedragen en datums, maar geen persoonsgegevens: geen namen, geen e-mailadressen, geen telefoonnummers, geen adressen en geen vrije tekst. Dat is een regel van de app, geen toevalligheid van deze versie; een AL-toets faalt zodra er iets daarvan in een payload staat. Hebt u de naam achter een aanvraag nodig, vraag die dan op bij het portaal of bij Business Central, met het nummer dat u hier kreeg.

Elke payload bevat deze sleutels:

SleutelTypeBetekenis
eventIdtekst (GUID)Wijst de gebeurtenis aan, niet de bezorging. Blijft gelijk bij nieuwe pogingen. Twee abonnementen op dezelfde gebeurtenis krijgen dezelfde eventId.
eventtekstEen van de waarden uit de tabel hierboven.
occurredAttekstWanneer het in Business Central gebeurde, ISO 8601 in UTC. Niet het moment van bezorgen.
companytekstDe naam van het Business Central-bedrijf.
documentTypetekstquote, order, shipment, invoice, request, account of ping.
documentNotekstOptioneel. Het document waar de gebeurtenis over gaat.
customerNotekstOptioneel. De klant waar de gebeurtenis over gaat.

En per gebeurtenis:

GebeurtenisExtra sleutels
QuoteReleasedstatus (Released), amountIncludingVat, currencyCode, documentDate, validUntil
QuoteAcceptedstatus (Accepted), amountIncludingVat, currencyCode, documentDate, lineCount
OrderCreatedquoteNo, amountIncludingVat, currencyCode, documentDate
ShipmentPostedorderNo (als de verzending uit een order komt), documentDate (de boekingsdatum)
InvoicePostedorderNo (als de factuur uit een order komt), amountIncludingVat, currencyCode, documentDate
RequestConvertedstatus (Converted), requestNo en quoteNo of orderNo
RequestRejectedstatus (Rejected), requestNo
AccountApprovedstatus (Approved), requestNo
AccountRejectedstatus (Rejected), requestNo
Pinggeen

amountIncludingVat is een JSON-getal in de valuta van het document; een lege currencyCode betekent de lokale valuta van het bedrijf. Datums zijn YYYY-MM-DD en een lege datum is "", zoals overal in deze naslag.

{
  "event": "InvoicePosted",
  "company": "Booischotse Metalen",
  "documentType": "invoice",
  "documentNo": "PS-INV101023",
  "customerNo": "C00010",
  "orderNo": "PS-ORD100411",
  "amountIncludingVat": 4235.18,
  "currencyCode": "",
  "documentDate": "2026-09-20",
  "eventId": "5b7a1f0c-2d43-4e18-9a55-0c7e3b6d81f2",
  "occurredAt": "2026-09-20T09:12:31.117Z"
}

De handtekening controleren

Een ontvanger die de handtekening niet kan controleren, vertrouwt iedereen die het adres kent. Het recept is kort, en er is geen excuus om het over te slaan. Elk abonnement heeft een eigen geheim van 64 hexadecimale tekens, dat u in Business Central aanmaakt met Geheim aanmaken en dat één keer wordt getoond. Zet het in de configuratie van uw server; daarna is het nooit meer te lezen: niet op de pagina, niet in een export en niet in een kopie van de database.

X-VOA-Signature = "sha256=" + lowercase_hex( HMAC_SHA256( secret, X-VOA-Timestamp + "." + raw_request_body ) )

Drie details bepalen of het werkt:

  1. Over de ruwe body. Bereken de handtekening over de bytes zoals ze binnenkwamen, voordat u de JSON ontleedt en opnieuw serialiseert. Witruimte en de volgorde van de sleutels horen bij wat er is ondertekend.
  2. Het tijdstip zit in de handtekening, niet ernaast. Daarom loont het de moeite om het te controleren: anders kon iedereen die een bezorging onderschepte, die morgen opnieuw versturen met een nieuwe X-VOA-Timestamp en een handtekening die nog steeds klopte.
  3. Vergelijk in constante tijd, en wijs een tijdstip af dat meer dan een paar minuten oud is of in de toekomst ligt.
import crypto from 'node:crypto'

// Express: express.raw({ type: 'application/json' }), dus de ruwe body, niet de ontlede.
export function handleWebhook (req, res) {
  const secret = process.env.VOA_WEBHOOK_SECRET
  const timestamp = req.get('X-VOA-Timestamp') || ''
  const received = req.get('X-VOA-Signature') || ''
  const rawBody = req.body                       // Buffer

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody.toString('utf8'))
    .digest('hex')

  const a = Buffer.from(received)
  const b = Buffer.from(expected)
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.status(401).end()                 // niet van Business Central
  }

  const age = Math.abs(Date.now() - Date.parse(timestamp))
  if (!Number.isFinite(age) || age > 5 * 60 * 1000) {
    return res.status(401).end()                 // herhaling, of een klok die ver afwijkt
  }

  const event = JSON.parse(rawBody.toString('utf8'))
  if (alreadyHandled(event.eventId)) return res.status(200).end()   // een nieuwe poging
  store(event)                                   // doe het werk waar mogelijk pas na het antwoord
  res.status(200).end()
}

Met deze proefwaarden toetst u uw implementatie zonder Business Central. Het zijn dezelfde waarden die de AL-toets gebruikt:

WatWaarde
geheimvoa-test-secret
X-VOA-Timestamp2026-09-20T09:12:33.000Z
body{"event":"InvoicePosted","documentNo":"PS-INV101023"}
handtekeningsha256=6687de2bb04779680fe96e12682e4a2b564bc4a3cc329edb2f551056de039e44

Eén seconde later, met 2026-09-20T09:12:34.000Z over dezelfde body, is het resultaat sha256=60bd375378f87dd5a30241d3c524a6a38d5f036222756e2727642488b73e8e92. Geeft uw implementatie voor beide tijdstippen hetzelfde antwoord, dan ondertekent u het tijdstip niet.

Nieuwe pogingen, dubbele bezorgingen en volgorde

  • Een bezorging wordt vijf keer geprobeerd, met 1, 5, 30 en 120 minuten ertussen. Daarna wordt ze geparkeerd en komt ze op de lijst Webhookbezorgingen in Business Central te staan, waar iemand haar met Nu opnieuw proberen terug in de wachtrij kan zetten.
  • Bezorgingen gaan via een wachtrij en worden nooit vanuit een boeking verstuurd. Een gebeurtenis die ontstaat terwijl er een factuur wordt geboekt, wordt in diezelfde transactie naar een wachtrijtabel geschreven, en een taakwachtrijpost doet de aanroep. Een traag of kapot endpoint kan een boeking dus nooit ophouden en nooit laten terugdraaien. Wordt de boeking teruggedraaid, dan verdwijnt de klaargezette gebeurtenis ook.
  • Reken op dubbele bezorgingen. Een bezorging die aan uw kant een time-out kreeg, kan best zijn aangekomen. Ontdubbel op eventId; die blijft bij nieuwe pogingen gelijk.
  • Reken niet op een vaste volgorde. Twee gebeurtenissen binnen dezelfde minuut kunnen in willekeurige volgorde aankomen, en een gebeurtenis die opnieuw wordt bezorgd, komt pas binnen na gebeurtenissen die later plaatsvonden. Gebruik occurredAt als de volgorde ertoe doet.
  • Na twintig mislukte pogingen op rij (instelbaar op CustomerGate-instellingen) zet een abonnement zichzelf uit, en het gaat nooit vanzelf weer aan. Iemand controleert het endpoint en kiest Aanzetten. Een omgeving die uitgaande HTTP-aanroepen blokkeert, telt niet als mislukking van het endpoint: dat is een instelling, geen kapotte ontvanger.

Antwoord met 2xx op een event dat u niet kent. In een compatibele release kunnen er waarden bij komen, en een ontvanger die een onbekende naam met een fout beantwoordt, zet het abonnement uit vanwege een gebeurtenis die hem niet eens aangaat.

voaPing: wat voor installatie dit is

Eén actie, POST .../ODataV4/voaPing_Ping?company={company id} met een lege body, die de vragen beantwoordt die u anders met proefaanroepen zou moeten uitzoeken: is dit productie of een sandbox, is Steel 365 geïnstalleerd (en zijn er dus drie aantallen per regel in plaats van één), waar komen de pdf's vandaan, is de inrichting af, en is dit een demobedrijf waarvan de mail nergens aankomt.

Er is geen sessietoken voor nodig en er komen geen persoonsgegevens terug: geen klantnaam, geen e-mailadres, geen aantallen. Roep de actie één keer aan als uw toepassing start, en nog eens na een uitrol. Een aanroep is goedkoop, maar dit is ook het enige endpoint dat een aanroeper zonder sessie iets vertelt, dus poll het niet.

{
  "app": {"name": "CustomerGate 365", "publisher": "Van Oosten Software", "version": "1.0.39.0"},
  "contractVersion": "1.0",
  "environment": {"type": "Production", "companyName": "CRONUS BE"},
  "modules": {"steel365": false, "steel365Archive": false},
  "documentSource": "report",
  "demoMode": false,
  "setupComplete": true,
  "supportedLocales": ["en-US", "nl-NL", "nl-BE", "fr-BE", "fr-FR"],
  "serverTimeUtc": "2026-09-20T11:24:07.421Z",
  "licence": {
    "status": "Active",
    "plan": "Portal",
    "validUntil": "2027-12-31",
    "servicesStopOn": "2028-01-31",
    "features": {"requests": true, "catalog": true, "portal": true, "attachments": true, "webhooks": false}
  },
  "attachments": {
    "enabled": true,
    "maxFileSizeKb": 10240,
    "maxPerRequest": 10,
    "allowedTypes": ["pdf", "png", "jpg", "jpeg", "dxf", "dwg", "step", "stp", "xlsx", "csv"],
    "deniedTypes": ["exe", "dll", "js", "vbs", "ps1", "bat", "cmd", "scr", "msi", "jar", "sh", "html", "htm", "svg"],
    "contentCheckedTypes": ["pdf", "png", "jpg", "jpeg", "xlsx", "dwg", "step", "stp"]
  }
}
SleutelTypeToelichting
app.name, app.publishertekstDe naam en de uitgever van de app, uit de module zelf.
app.versiontekstDe appversie, vier getallen. Verandert bij elke build.
contractVersiontekstDe versie van dit koppelvlak, niet van de app. Verandert alleen als de afspraak verandert.
environment.typetekstProduction of Sandbox.
environment.companyNametekstDe naam van het Business Central-bedrijf waarin de aanroep liep. Een codeunitservice wordt met ?company={guid} aangeroepen, dus dit is de enige manier om te zien welk bedrijf dat was. Het is geen klantnaam.
modules.steel365booleaniFacto Steel 365 (DXSteel) is geïnstalleerd. Bepaalt de vorm van DocumentLines.
modules.steel365ArchivebooleanDXSteel Doc Archive is geïnstalleerd.
documentSourcetekstarchive, report of none. Waar de pdf's in het portaal vandaan komen; zie de tabel hieronder.
demoModebooleanDe demostand staat aan: elk bericht van CustomerGate gaat naar één demoadres in plaats van naar de klant.
setupCompletebooleanfalse zolang het instellingenrecord ontbreekt, een nummerreeks leeg is of de client-id van de website niet is ingevuld. false betekent dat aanroepen mislukken; meld dat op uw eigen statuspagina in plaats van te gissen naar de oorzaak van een 403.
supportedLocalesarray van tekstDe talen waarin de app is vertaald. Gebruik een daarvan in Accept-Language.
serverTimeUtctekstDe klok van de server, ISO 8601 in UTC. Handig om een klokverschil op te merken voordat het tot een verlopen sessie leidt.
licence.statustekstDe huidige stand van de licentie in dit bedrijf: NotRequired, Demo, Trial, Active, Expiring, Grace, EnvironmentChanged, Lapsed of CompanyNotCovered. Alleen de laatste twee leggen de webservices stil.
licence.plantekstRequests, Portal of Complete; zonder geldige sleutel None. Complete in een sandbox, in de demostand en tijdens de proefperiode.
licence.validUntiltekstDe laatste dag van de licentie (yyyy-mm-dd), of leeg als er geen sleutel is.
licence.servicesStopOntekstDe eerste dag waarop de webservices stoppen (of stopten) als er niets verandert, of leeg als dat niet in zicht is.
licence.featuresobjectPer functie of een aanroep nu zou lukken: requests, catalog, portal, attachments, webhooks.
attachments.enabledbooleanSinds 1.0.44. Of AddAttachment nu werkt: Bijlagen ingeschakeld staat aan in dit bedrijf en de licentie dekt bijlagen (licence.features.attachments). false betekent: toon geen uploadveld.
attachments.maxFileSizeKbgetalSinds 1.0.44. Het grootste bestand dat AddAttachment accepteert, in kilobytes gedecodeerde inhoud, met de terugvalwaarde toegepast: 0 in de instellingen is hier 10240.
attachments.maxPerRequestgetalSinds 1.0.44. Hoeveel bestanden één aanvraag mag hebben. 0 in de instellingen is hier 10.
attachments.allowedTypesarray van tekstSinds 1.0.44. De extensies die AddAttachment accepteert, in kleine letters, zonder punt, elk één keer, precies zoals de controle bij het uploaden ze leest. Een waarde in Toegestane soorten bijlagen die op de weigerlijst staat, een punt bevat (.pdf) of langer is dan tien tekens, kan nooit een bestand doorlaten en valt weg. Een lege instelling geeft de standaardlijst.
attachments.deniedTypesarray van tekstSinds 1.0.44. De vaste weigerlijst uit de code. Geweigerd op elk deel van een bestandsnaam, wat de instellingen ook zeggen (drawing.exe.pdf wordt geweigerd).
attachments.contentCheckedTypesarray van tekstSinds 1.0.44. De extensies waarvan de eerste bytes bij de extensie moeten passen. Een soort die is toegestaan maar niet in deze lijst staat (dxf, csv), wordt op zijn naam geaccepteerd.

documentSource verdient een eigen toelichting, want die bepaalt welk van twee downloadpaden uw site moet bouwen:

WaardeBetekenisWat u bouwt
archiveDXSteel Doc Archive is geïnstalleerd.MyDocuments geeft archiefrijen terug; haal de bytes met de id op bij de eigen API van iFacto. Document geeft altijd "" terug.
reportGeen archief, en Portaaldocumenten uit rapporten staat aan.MyDocuments geeft rapportrijen terug; download via Document.
noneGeen archief en de instelling staat uit.Helemaal geen documenten. MyDocuments is altijd leeg en Document geeft altijd "" terug; toon geen downloadknop.

Zoals bij elke codeunitservice is het antwoord een tekenreeks binnen value: ontleed die een tweede keer. voaPing heeft de machtigingenset VOA WEB CONTRACT nodig; Machtigingen herstellen wijst die samen met de andere drie toe aan de Entra-toepassing van de website. Een omgeving die al was ingericht voordat deze service bestond, krijgt de service bij de volgende update van de app en de set bij de volgende keer dat Machtigingen herstellen wordt uitgevoerd; tot die tijd antwoordt de aanroep met 403.

voaPing stopt nooit vanwege de licentie: juist hiermee komt een website te weten waarom de rest stopte. Welke licentiefunctie elke aanroep nodig heeft, staat per procedure en per pagina als licence in het contractbestand.

Foutcodes

Een zin is geen houvast voor uw code. De zin wordt vertaald, want de aanroep stuurt een Accept-Language mee, en hij wordt herschreven zodra iemand hem duidelijker formuleert: twee manieren om uw foutafhandeling kwijt te raken zonder dat er iets op rood springt.

Elke fout die een client kan veroorzaken, heeft daarom een machineleesbaar merkteken aan het begin van de melding:

[VOA-PrivacyConsentMissing] A request cannot be accepted without agreement to the privacy statement. Send privacyAccepted = true.
  • Het merkteken bestaat uit [VOA-, de code en ], staat aan het begin van de melding en wordt nooit vertaald.
  • De melding erachter is ongewijzigd en blijft in de taal van de sessie.
  • Laat uw code afhangen van het merkteken, nooit van de zin.
  • Het oudere merkteken [VOA-DUBBEL] staat nog steeds in de melding over een dubbele requestId, waar het altijd stond. Het is niet vervangen, zodat een website die de codes pas later leert kennen, niet stukgaat.

De codes

CodeWaarWat er gebeurdeWat een client moet doen
RequestsClosedCreateQuoteRequest, CreateAccountRequestAanvragen weigeren staat aan in de instellingen: CustomerGate neemt tijdelijk niets aan.Toon dat versturen tijdelijk niet mogelijk is. Dit is de enige code waarbij u het later opnieuw mag proberen. Verstuur niet automatisch opnieuw.
PrivacyConsentMissingCreateQuoteRequest, CreateAccountRequestprivacyAccepted was niet true, of privacyConsentVersion was leeg.Een fout in het formulier. Toon het selectievakje voor de toestemming; probeer het niet opnieuw.
InvalidInputCreateQuoteRequest, CreateAccountRequestDe payload is geen geldige JSON, een verplichte waarde ontbreekt (email bij een accountaanvraag), of een waarde is niet te lezen (requestedDeliveryDate is geen datum). De melding noemt de eigenschap.Herstel de invoer. Probeer het niet opnieuw met dezelfde payload.
DuplicateRequestCreateQuoteRequest, CreateAccountRequestDeze requestId is al aangenomen. De melding bevat ook [VOA-DUBBEL] en het nummer van de opgeslagen aanvraag.Beschouw het als geslaagd. De aanvraag staat in Business Central.
SetupIncompleteCreateQuoteRequest, CreateAccountRequestCustomerGate is niet ingericht in dit bedrijf: er is geen nummerreeks voor de aanvragen.Niet het probleem van de bezoeker: waarschuw wie de omgeving beheert. voaPing meldt hetzelfde als setupComplete: false.
ItemNotOnWebsiteCreateQuoteRequestEen regel verwijst naar een artikel dat niet in de webcatalogus staat.Een fout of een verouderde pagina: de bezoeker kijkt naar iets wat niet online te koop is. Ververs de catalogus en laat de regel weg.
ItemNotQuotableCreateQuoteRequestEen regel verwijst naar een artikel dat wel in de catalogus staat, maar niet online bestelbaar is (webQuotable is false).Toon voor dat artikel "prijs op aanvraag" in plaats van een bestelknop, en bied het contactformulier aan.
CatalogNotComputedvoaItems, voaVariants, voaCategories, voaCatalogMediaDe webcatalogus is in dit bedrijf nog nooit berekend, dus Business Central kan niet zeggen welke artikelen op de website horen.Stop de synchronisatie en bewaar wat u al hebt. Vat dit niet op als een lege catalogus. Wie de omgeving beheert, kiest Webcatalogus herberekenen op CustomerGate-instellingen.
LicenceRequiredElke service behalve voaPing en SignOutEr is geen geldige licentie voor deze omgeving (na de proef- of respijtperiode), of de licentie geldt niet voor dit bedrijf. Er is niets gelezen of geschreven.Toon dat de functie tijdelijk niet beschikbaar is en bewaar wat u al hebt. Niet het probleem van de bezoeker: voaPing vertelt waarom en sinds wanneer. Opnieuw proberen heeft geen zin zolang er geen sleutel is.
NotInPlanvoaPortal en AddAttachment bij het pakket Requests. In een lager pakket dan Complete geven webhooks geen foutcode: er komt niets in de wachtrij en er wordt niets verstuurd.Het pakket van deze omgeving omvat deze functie niet, en dat blijft zo tot het pakket verandert.Verberg de functie. voaPing noemt in licence.features wat werkt.
NotAuthorizedAcceptQuote, AcceptQuoteWithDetails, RejectQuote, SwitchCustomer, InviteColleague (als errorCode)De aanroep is geweigerd. Altijd dezelfde code, om welke reden dan ook, op alle vijf de plekken.Toon dat het niet kan. Zie hieronder.
QuoteNotOpenAcceptQuote, AcceptQuoteWithDetails (als errorCode)De geldigheidsdatum van de offerte is verstreken.Toon dat de offerte verlopen is en bied aan om een nieuwe aan te vragen.
StaffOrdersDisabledCreateQuoteRequestsource is staffOrder, maar Orders via medewerker ingeschakeld staat uit op CustomerGate-instellingen. Er is niets opgeslagen.Geen probleem van de bezoeker: wie de omgeving beheert zet de instelling aan, of orders via medewerker zijn niet voor dit bedrijf bedoeld. Verstuur hem niet opnieuw als gewone aanvraag.

errorCode bij AcceptQuote en verwante procedures

AcceptQuote werpt geen fout op, maar antwoordt met een object. Alleen als de uitkomst een mislukking is, bevat dat object naast de sleutels die het al had ook een errorCode.

UitkomsterrorCodeToelichting
recordedafwezigHet akkoord is binnen.
alreadyafwezigEr was al een akkoord; geen fout.
expiredQuoteNotOpenDe offerte is aantoonbaar van deze klant, en de geldigheidsdatum is verstreken.
refusedNotAuthorizedAltijd dezelfde code, om welke reden dan ook.

Die laatste regel is een bewuste keuze en verandert niet. Een offerte van een andere klant, een nummer dat niet bestaat, een offerte die niet is vrijgegeven en een Meekijker die niet mag accepteren, leveren allemaal hetzelfde antwoord op, want elk ander antwoord maakt van dit endpoint een gratis manier om na te gaan wat er bestaat en van wie het is. De code geeft u een stabiel houvast, geen reden. RejectQuote, SwitchCustomer en InviteColleague volgen hetzelfde patroon: errorCode verschijnt alleen bij refused en is dan altijd NotAuthorized. De uitkomsten already en throttled zijn geen fouten en hebben geen code.

Codes die bestaan maar nooit worden teruggegeven

De lijst met codes bevat ook SessionExpired, SessionUnknown, SignInCodeInvalid, SignInCodeExpired, SignInCodeThrottled, AccountNotActive, AccountUnknown, DocumentNotFound, DocumentNotAllowed, QuoteChanged, RequestNotFound, ModuleNotAvailable en Internal. Het portaal geeft ze op dit moment nooit terug, en dat is niet over het hoofd gezien: voaPortal beantwoordt een ongeldige sessie, een document van een andere klant en een document dat niet bestaat bewust met precies hetzelfde lege antwoord. De namen zijn gepubliceerd zodat ze vanaf het begin bij het koppelvlak horen, en zodat een latere versie er een kan gebruiken zonder de lijst te hoeven uitbreiden. In het contractbestand zijn ze gemarkeerd met "reserved": true.

Het contract als bestand en de client

Alles hierboven is proza. Een generator heeft gegevens nodig, dus hetzelfde koppelvlak wordt ook als bestand gepubliceerd: contract/customergate-365.contract.json. Het wordt uit de AL-broncode gegenereerd, met de app meegeleverd en bij elke pull request opnieuw gegenereerd en vergeleken, zodat een wijziging aan het koppelvlak als verschil zichtbaar wordt en nooit ongemerkt gebeurt.

SleutelInhoud
contractVersionDezelfde waarde die voaPing teruggeeft.
appId, naam, uitgever, versie en de ondersteunde talen.
services[]Elke gepubliceerde service, met objectsoort en objectnummer.
services[].procedures[]Bij een codeunitservice: elke aanroepbare procedure, de naam van de OData-actie (voaPortal_MyQuotes), de parameters met hun AL-typen, het retourtype, de JSON-sleutels die ze schrijft (jsonKeys) en de sleutels die ze uit de payload leest (payloadKeys, bij voaRequests).
services[].fixedValues[]Per JSON-sleutel de vaste waarden die erin kunnen staan, met de naam van de AL-helper waar ze vandaan komen. status staat er twee keer in, omdat de status van een aanvraag en die van een document verschillende lijsten zijn.
services[].properties[]Bij een paginaservice: de OData-eigenschapsnamen.
enums[]Elke enum van de app met de namen van de waarden: dezelfde als onder Vaste waarden hieronder.
errorCodes[]Elke code uit Foutcodes, met het merkteken, de plek waar hij wordt opgeworpen en of hij gereserveerd is.

Wat er met opzet niet in staat: welke sleutel optioneel is, welke sleutel alleen met Steel 365 verschijnt, wat een waarde betekent en welke procedure alleen leest. Dat is redactionele inhoud en staat op deze pagina. Gebruik het bestand voor de namen en de structuur, en deze pagina voor de regels.

De gegenereerde TypeScript-client

Bouwt u in TypeScript op deze app, dan hoeft u het technische werk van deze pagina niet zelf te schrijven. Er is een clientpakket dat uit het contractbestand wordt gegenereerd, dus het kan geen service beschrijven die niet bestaat en geen service overslaan die wel bestaat.

const cg = createClient({ tenant, environment, company: { id, name }, getToken });
const session = await cg.voaPortal.readSession({ token });   // al twee keer ontleed

Wat het u uit handen neemt:

  • De OData-envelop en het tweede ontleden, per procedure afgeleid uit returns en jsonKeys in het contract: een tekstantwoord met JSON-sleutels wordt ontleed, een sessietoken niet.
  • Het lege antwoord: "" komt binnen als null, wat de reden ook is.
  • De parameternamen, die vóór de aanroep worden gecontroleerd, omdat OData op naam bindt en een verkeerde naam een kale 400 oplevert.
  • [VOA-…] als een getypeerde code op een foutobject, met een aparte vlag voor een dubbele verzending, inclusief het oudere [VOA-DUBBEL].
  • De regels voor opnieuw proberen: altijd na een 429, één keer na een geweigerd token, en verder nooit, tenzij u zelf een procedure als lezend aanmerkt.
  • De vaste waarden en de waardenamen van de enums, als union-typen.
  • Pagineren via @odata.nextLink bij de catalogusservices.

Het pakket heeft geen runtime-afhankelijkheden, leest nooit een omgevingsvariabele en bewaart nooit een geheim: u geeft het een functie mee die een token teruggeeft. Het versienummer volgt contractVersion en niet de versie van de app; 1.0.x hoort bij contract 1.0, en het derde getal is voor correcties in het pakket zelf. De generator weigert te draaien als die twee niet overeenkomen.

Het contractbestand en het clientpakket krijgt u van Van Oosten Software; het pakket staat nog niet op npm, dus u installeert het uit de broncode die u hebt gekregen. Controleer bij het opstarten waarmee u praat, door contractVersion uit voaPing te vergelijken met de versie waarvoor uw client is gebouwd.

Vaste waarden

Deze waarden liggen vast. Ze worden nooit vertaald en hangen nooit af van de taal van de sessie. Vergelijk ze exact.

WaarSleutel of parameterWaarden
ReadSession, MyDetailsroleBuyer, Viewer
ReadSession, MyDetailscustomers[].roleBuyer, Viewer
MyRequestsstatusNew, InProgress, Converted, Rejected, ""
MyRequestsdocumentTypequote, order
MyQuotes, MyOrdersstatusOpen, Released, ""
MyRequests, MyOrdersquoteNoStatusactive, completed, ""
MyQuotes, MyShipments, MyDocumentsorderNoStatusactive, completed, ""
MyDocumentsdocumentNoStatusactive, completed
DocumentLineslineStatusOpen, PartlyShipped, Shipped, Invoiced
AcceptQuote, AcceptQuoteWithDetailsoutcomerecorded, already, expired, refused
RejectQuoteoutcomerecorded, already, refused
RejectQuoteparameter reasonCodeTooExpensive, DeliveryTooLate, OrderedElsewhere, NoLongerNeeded, Other (elke andere waarde wordt opgeslagen als NotStated)
MyQuotesrejectionReasonTooExpensive, DeliveryTooLate, OrderedElsewhere, NoLongerNeeded, Other, NotStated, "" (sinds 1.0.44)
CreateQuoteRequest, RequestLinesregel inspectionCertificateNone, Certificate22, Certificate31, Certificate32 (bij invoer wordt elke andere waarde None) (sinds 1.0.44)
CreateQuoteRequest, RequestLinesregel processing (array)CutToLength, MitreCutting, Drilling, Punching, ThermalCutting, Bending, Blasting, Priming, Galvanizing, Painting (bij invoer wordt elke andere waarde overgeslagen) (sinds 1.0.44)
SwitchCustomeroutcomeswitched, refused
InviteColleagueoutcomecreated, already, throttled, refused
InviteColleagueparameter roleBuyer, Viewer (elke andere waarde: Viewer)
MyNotificationSettings, SetNotificationSettingssleutelsenabled, quoteReleased, orderConfirmed, shipmentPosted, invoicePosted, requestReceived
DocumentLinesparameter documentTypequote, order, shipment
Documentparameter documentTypequote, order, shipment, invoice, creditMemo
MyDocumentsdocumentTypequote, order, shipment, ""
MyDocumentscategorySalesQuote, SalesOrder, SalesInvoice, SalesCreditMemo, Proforma, Waybill, WarehouseShipment
MyDocumentssourcereport (alleen rapportrijen)
MyDocumentsreportTypequote, order, shipment, invoice, creditMemo
MyDocuments, DocumentcontentTypeapplication/pdf, application/octet-stream
MyAttachmentsoriginPortalUser, InsideSales
MyAttachments, AttachmentcontentTypeAfgeleid uit de extensie: application/pdf, image/png, image/jpeg, image/vnd.dxf, image/vnd.dwg, model/step, text/csv, de Office-typen, anders application/octet-stream
AddAttachmentweigerlijstexe, dll, js, vbs, ps1, bat, cmd, scr, msi, jar, sh, html, htm, svg: ligt vast in de code en kan door geen instelling worden overschreven; voaPing geeft die terug als attachments.deniedTypes (sinds 1.0.44)
ReadSession, DocumentLinesweightTypeTheoretical, Trade, GermanTrade, "" (Steel 365)
DocumentLinesunitOnvertaalde optienamen van het Steel 365-veld; de referentiewebsite verwerkt PCS, KG, MTR
voaRemovalsKindItem, Variant, Category
voaCatalogMediacontentTypeWat Business Central heeft opgeslagen, bijvoorbeeld image/jpeg of image/png; application/octet-stream als dat onbekend is
CreateQuoteRequestdeliveryMethodPickup, Delivery (elke andere waarde: Pickup)
CreateQuoteRequestsourceVrije tekst tot 50 tekens. staffOrder maakt er een order via medewerker van (alleen als Orders via medewerker ingeschakeld aanstaat); LinkRequest zet portal.
CreateAccountRequestexistingCustomerYes, No, Unknown (elke andere waarde: Unknown)
WebhookeventPing, QuoteReleased, QuoteAccepted, OrderCreated, ShipmentPosted, InvoicePosted, AccountApproved, AccountRejected, RequestConverted, RequestRejected
WebhookdocumentTypequote, order, shipment, invoice, request, account, ping
WebhookX-VOA-Signaturesha256= plus 64 hexadecimale tekens in kleine letters
Pingenvironment.typeProduction, Sandbox
PingdocumentSourcearchive, report, none
PingcontractVersion1.0
Fouten van voaRequestsmelding[VOA-DUBBEL] markeert een dubbele requestId
Fouten van de catalogusmelding[VOA-CatalogNotComputed], [VOA-ItemNotOnWebsite], [VOA-ItemNotQuotable]
Elke fout die een client kan veroorzakenmeldingBegint met [VOA-<code>]; zie Foutcodes
Aanmeldcodealfabet0123456789ABCDEFGHJKMNPQRSTVWXYZ, 10 tekens

Aanvraagnummers komen uit nummerreeksen in de instellingen. Bij installatie staan die standaard op WEB000001 tot WEB999999 voor offerteaanvragen en ACC000001 tot ACC999999 voor accountaanvragen, maar elk bedrijf kan ze wijzigen: ga niet uit van dit patroon.

Verschillen met Steel 365

CustomerGate 365 is één app. Die controleert zelf de omgeving en heeft daar geen instelling voor nodig: Steel 365 is actief zodra het veld Quantity Pieces DMX op de verkoopregel bestaat, en het documentarchief van Steel 365 zodra de tabel Document Archive DADMX bestaat.

OnderdeelMet Steel 365Zonder Steel 365
CatalogusservicesvoaProfiles, voaGrades en voaItemTypes zijn gepubliceerd, en de DX…-eigenschappen zijn gevuldAlleen voaItems, voaVariants, voaCategories, voaRemovals en voaCatalogMedia; DX…-tekst is "" en getallen zijn 0
Machtigingen voor synchronisatieVOA WEB SYNC en VOA CG S365 SYNCAlleen VOA WEB SYNC
Aantallen in DocumentLinesquantityPieces, quantityMeters, quantityKg plus unit; quantity komt nooit voorquantity plus unitOfMeasureCode
DocumentLines bij actuele orderregelsoutstandingPieces/Meters/Kg, shippedPieces/Meters/KgoutstandingQuantity, quantityShipped
Extra sleutels in DocumentLinesprofile, grade, formatDescription, weightType als ze gevuld zijn; sinds 1.0.44 lengthMm, widthMm, thicknessMm en customFormat uit de variant van de regelgeen
requestedDeliveryDate, promisedDeliveryDate en lineStatus per regelaanwezig bij actuele orderregels; lineStatus komt uit de standaardaantallenhetzelfde: deze drie zijn niet van Steel 365 afhankelijk
MyQuotes.totalWeightKgaanwezigafwezig
ReadSession.weightTypehet gewichtstype van de klant, of """"
Geldigheid van een offertevalt terug op de vervaldatum uit Steel 365alleen de geldigheidsdatum van de offerte

Het documentarchief is een aparte schakelaar:

OnderdeelMet het archiefZonder archief
MyDocumentsArchiefrijen, gefilterd op de toegestane rubriekenRapportrijen als Portaaldocumenten uit rapporten aanstaat, anders leeg
Pdf downloadenDe bound action van de iFacto-API met de idDocument geeft base64 terug
Documentaltijd ""drukt de pdf af
hasDocumentaltijd falsetrue als er een rapport is ingesteld
Offertes en orders die alleen nog in het archief staanorderNo- en quoteNo-verwijzingen uit het archiefgeen verwijzingen
Tenant-set van de websiteVOA CG S365 ARCHIVEVOA CG DOCUMENTS als de instelling aanstaat
Ping.documentSourcearchivereport, of none als de instelling uitstaat

Versies en compatibiliteit

De namen zijn het contract. Websites gebruiken ze letterlijk, dus dit hoort er allemaal bij: de namen van de services, de namen van procedures en parameters inclusief hoofdlettergebruik, de namen van de cataloguseigenschappen, de JSON-sleutels in payloads en antwoorden, elke vaste waarde hierboven, de foutcodes en de vorm [VOA-…] van het merkteken. Wie er één van hernoemt, maakt elke website die op de app is gebouwd stuk, en vaak zonder dat iemand het merkt: een hernoemde cataloguseigenschap laat gewoon een kolom leeg, en een hernoemde JSON-sleutel wordt gelezen als null. Er zijn geen aliassen voor eerdere namen.

Voor de webhooks gelden dezelfde regels: de namen van de headers, de sleutels in de payload, de waarden van event en het recept voor de handtekening horen bij het koppelvlak. Een nieuwe waarde van event vraagt om een wijziging aan de ontvangende kant voordat klanten hem zien, en een ontvanger die een onbekende naam niet met 2xx beantwoordt, zet zijn eigen abonnement uit vanwege iets wat hem niet aangaat.

Voor wie de services gebruikt:

  • Negeer sleutels die u niet kent. In een compatibele release kunnen er sleutels bij komen.
  • Behandel een ontbrekende optionele sleutel als null, en "" als geen waarde.
  • Behandel een onbekende waarde als onbekend. Zet haar niet om naar een standaardwaarde die iets beweert, bijvoorbeeld door een onbekende outcome als recorded te lezen.
  • De waarden van inspectionCertificate en processing (sinds 1.0.44) kunnen groeien: een andere app kan er een toevoegen, en die komt dan onder zijn eigen naam terug uit RequestLines. Behandel een naam die u niet kent als onbekend.
  • Houd het sessietoken, de requestId, het webhookgeheim en de sleutel voor de IP-hash aan de serverkant.

Een sleutel, een procedure of een cataloguseigenschap toevoegen is een compatibele wijziging; er een verwijderen of hernoemen niet. Een parameter toevoegen aan een bestaande actie evenmin, omdat OData op naam bindt: daarom staat AcceptQuoteWithDetails naast AcceptQuote in plaats van erin. Installaties en updates maken ontbrekende services aan, maar wijzigen nooit een bestaande: een hernoemde service zou naast de oude komen te staan, terwijl de oude blijft verwijzen naar hetzelfde als voorheen.

contractVersion is niet de appversie. De app krijgt bij elke build een nieuw versienummer van vier getallen, terwijl contractVersion alleen verandert als er aan deze afspraak iets verandert dat niet compatibel is. Controleer bij het opstarten dus contractVersion uit voaPing, niet app.version.

Verder in de documentatie

  • Installatiegids

    Van het installeren tot een eerste aanvraag, en verder: de taakwachtrijposten, de webcatalogus, webhooks en de diagnose.

  • Versiegeschiedenis

    Wat er in elke versie van de app zit.

  • Ondersteuning

    Hoe u hulp vraagt, en wat u meestuurt.