Quote Connector REST API Proposal - Feedback wanted

Publisert av Christian Mogensen, 4. mai 2022

The existing Online Quote Connector uses WCF, which is a bit heavy and old-fashioned. Here is our proposal for a new REST based API that should be easier to implement.


API is very similar to WCF - since both follow the internal IQuoteConnection interface.

Authentication is simpler, but still flexible.
The number of API calls should be lower than with WCF.

NetServer / CRM.web is acting as a client towards a partner provided URL.

The partner provided URL points to a REST API. The partner can implement the REST API in whatever tech they like.

The partner implements its own /Authentication endpoint as part of this API, so you decide if you need per-user access tokens or a shared secret authentication.

Comments? What do you think?

Any other pain-points we should look at in the Quote API?


Changes from WCF Connector API

Authentication is explicit rather than based on WCF certificate configuration.

Initialize is dropped from the API

Capabilities are fetched once and cached instead of being called frequently.

Use HTTP POST instead of SOAP/WCF requests.

NetServer is more clearly the client.


REST API Proposal

Follows IQuoteConnector - very similar to WCF API

Drop a couple of methods that we can simplify.

Online only - will not be available on-site, since on-site can code directly against .net APIs

  • POST  url/GetConfigurationFields
  • POST  url/TestConnection
  • POST  url/GetCapabilities
  • POST  url/GetActivePriceLists
  • POST  url/GetAllPriceLists
  • POST  url/GetAddresses
  • POST  url/GetNumberOfProductImages
  • POST  url/GetProductImage
  • POST  url/GetOrderState
  • POST  url/GetProduct
  • POST  url/GetProducts
  • POST  url/GetQuoteLinesFromProduct
  • POST  url/GetQuoteList
  • POST  url/OnAfterSaveQuote
  • POST  url/OnAfterSentQuoteVersion
  • POST  url/OnBeforeCreateQuote
  • POST  url/OnBeforeCreateQuoteAlternative
  • POST  url/OnBeforeCreateQuoteVersion
  • POST  url/OnBeforeDeleteQuote
  • POST  url/OnQuoteLineChanged
  • POST  url/PlaceOrder
  • POST  url/RecalculateQuoteAlternative
  • POST  url/UpdateQuoteVersionPrices
  • POST  url/ValidateQuoteVersion
  • POST  url/FindProduct
  • POST  url/GetSearchableFields
  • POST  url/GetSearchResults



Must be valid HTTPS, same rules as webhooks target. That means no self-signed certs, no invalid or expired certs.

  • Shared secret or signed JWT
    • Partner creates shared secret when creating connection.
    • Define shared secret in REST connection configuration
    • Stored in fileset db as connection property
  • Signed JWT: created automatically if no shared secret defined in connection.
    • NetServer generates JWT with claims
    • NetServer signs JWT with SuperOffice private key
    • Partner verifies JWT signature and returns access token.

Shared secret means all users share the same authentication to the partner's Quote Connector API.

Signed JWT means that each user will have their own authentication to the partner's QuoteConnector API.


Authentication with Shared Secret

Fields: URL + AppId+ secret 

Click TEST in SuperOffice admin GUI

  • NetServer: Verify that AppId is valid for this tenant
  • NetServer calls URL/Authenticate w secret as authentication
    • POST url/Authenticate
      Authorization: BEARER secret
      • Partner verifies secret and returns access token (which could just be the secret)
    • POST url/TestConnection
      Authorization: BEARER secret
      Accept: application/json
      Content-type: application/json

      { … json… }
      • Partner's Quote Connector Server looks up secret to find right config.


Authentication with JWT

Fields: URL +  AppId

  • Secret = blank            --> use JWT

Click TEST in SuperOfficeadmin GUI

  • NetServer: Verify that AppId is valid for this tenant 
  • NetServer calls URL/Authenticate w signed JWT as authentication
    • POST url/Authenticate
      Authorization: BEARER { signed-jwt }
      • Partner verifies JWT signature and returns access token
    • POST url/TestConnection
      Authorization: BEARER access-token
      Accept: application/json
      Content-type: application/json

      { … json… }
      • Partner's Quote Connector Server looks up access token to find right config.


Sequence diagram



The user clicks the CREATE QUOTE button in CRM.web

The CRM.web app calls CreateAndSaveQuote on NetServer

NetServer finds the right quote connector, initializes it with the parameters from its configuration, and calls OnBeforeSaveQuote on it.

The REST Quote Connector checks itself: do I have an shared secret defined in the configuration?

If there is a shared secret defined in the connection configuration:

  • Call the Authentication REST API endpoint with the Shared Secret.
  • The REST API endpoint verifies the shared secret - it needs to be unique for each customer - and returns the shared secret back as the access token.
  • The Quote Connector calls the REST API endpoint OnBeforeCreateQuote with the shared secret.
  • The REST API endpoint verifies the shared secret again, and returns the quote object.

If there is no shared secret in the connection configuration:

  • Generate a JWT with the customer id, serial number, user's name, id, e-mail address
  • Sign the JWT with the SuperOffice private key
  • Call the Authentication REST API endpoint with the signed JWT.
  • The REST API endpoint verifies the signature on the JWT
    • if the signature is ok, then extract the claims
    • Dig out the settings for the customer based on the claims
    • Map the user id/e-mail from the claims to a corresponding ERP user if you want
    • Generate an access token for the user
    • Return the access token
  • The Quote Connector calls the REST API endpoint OnBeforeCreateQuote with the access token.
  • The REST API endpoint verifies the access token and maps it to the customer, and returns the quote object.

NetServer calls OnAfterSaveQuote on the REST Quote Connector

OnAfterSaveQuote does not need to authenticate first - since we have a access token / secret from earlier.

The QuoteConnector calls the REST endpoint for OnAfterSaveQuote with the access token.

The REST API verifies that the access token is ok, and the returns the quote object.

The Quote object is passed back up to the UI.


API Contract

  • Swagger/OpenAPI definition for easy implementation by partner.
  • Also provide sample implementations, one for shared secret, one for JWT authentication.


REST API Configuration Parameters

What you would see in the Quote Admin Connection dialog:

  • URL:  https://partner.com/quotecon/     (required)
  • Secret:  xyzzy1234                               (optional)
  • App Id: 12345def                                 (optional)
  • Custom fields as defined by partner URL

This is pretty similar to the WCF connector, but with the additional shared secret field.


Get all capabilities up front, then cache

NetServer should call REST API once to get values:

  • POST /api/GetCapabilities
    ["cap1", "cap2", "cap3" ] 
    returns { "cap1" = true, "cap2" = false, "cap3"=true }

Then cache the result and use it until user logs out.

Should cut number of REST calls by 90% compared with WCF



Reauthenticate automatically on 401

If partner returns 401 then we must re-authenticate

  • call Partner API /Authenticate again to get fresh bearer token.

Update session cache with new token


Remove API bits you don't want to support.

  • 404 Error = not supported.
  • We cache these, so we don't call the missing endpoint again.



Comments? What do you think?

What other pain-points are there in the Quote API?


This seems like a more modern approach. I like everything about it.
Lars Madsen 28. feb. 2023