Twinfield is an online financial accounting software package for small and medium enterprises, produced and maintained by Twinfield International, based in the Netherlands. 

By executing http requests from your Betty Blocks application, you can integrate with your Twinfield environment. For example, when a product is sold in your webshop application, a request is sent to your Twinfield evironment to update your bookkeeping. The other way around would be if a payment has been fulfilled, you can update a record in your application.

In this article we'll show you how to connect with them by using their API. All functions and options are explained in their API documentation, so let's start off taking a look over there: Twinfield API documentation

Note: Knowledge about performing webrequests, XML formatting, Xpath and Twinfield is required to complete this HowTo. 

The Basics

To perform requests to Twinfield, we need to include Twinfield as a Webservice in our application. This is done in roughly 2 steps:

  • Perform an authentication request (https://login.twinfield.com/webservices/session.asmx?wsdl)
  • Perform a request for an actual operation (https://<cluster>.twinfield.com/webservices/<service>?wsdl)

It always starts with the first and then followed by the second. This also requires us to add 2 webservices to our application. Let's start with the first one.

The Authentication

Before we can access Twinfield's API and request our data, we need to prove to Twinfield we are an authorized user. This means we have to perform a login.

Go to Webservices and add a new Webservice. Enter the following values in the corresponding fields:

  • Name: Twinfield Authentication
  • Protocol: HTTPS
  • Host: "login.twinfield.com"
  • Authentication Type: None
  • Request Content-Type: Other
  • Response Content-Type: Other
  • Headers: Content-Type : text/xml

Save the webservice and click the + next to Endpoints (0) to create a Login webservice endpoint

A new form opens. The values entered in these fields will define to which endpoint the request will be sent to. Enter the following values in the corresponding fields:

  • Name: Login
  • Http method: POST
  • Path: "/webservices/session.asmx?wsdl" 
  • Request Content-Type: [inherit]
  • Response Content-Type: [inherit]
  • Template: - New -

Save the endpoint and you'll see how a new template is generated which can be accessed on the right side of the form.
We'll be using this template to define the message which is included in our request. In this case, this template needs our username, password and organization. Open and edit the template.
You can also rename it, this will make it easier to recognize. We called it Twinfield: Login.

The template is still empty, so enter the following value:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XmlSchema-instance" xmlns:xsd="http://www.w3.org/2001/XmlSchema">
<soap:Body>
<Logon xmlns="http://www.twinfield.com/">
<user>user</user>
<password>password</password>
<organisation>organisation</organisation>
</Logon>
</soap:Body>
</soap:Envelope>

Make sure to replace the values in <user>, <password> and <organisation> with your own credentials or the request won't work.

Save the template and go back to the endpoint. Click Run test in the top-right of the page to check if the request can be successfully executed. If everything went according to plan, the response looks like this:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <Header xmlns="http://www.twinfield.com/">
            <SessionID>d39f2a37-xxxx-xxxx-xxxx-xxxxxxxxxxxx</SessionID>
            <AccessToken />
        </Header>
    </soap:Header>
    <soap:Body>
        <LogonResponse xmlns="http://www.twinfield.com/">
            <LogonResult>Ok</LogonResult>
            <nextAction>None</nextAction>
            <cluster>https://c4.twinfield.com</cluster>
        </LogonResponse>
    </soap:Body>
</soap:Envelope>

In the <SessionID> value, we can see the identifier we received from Twinfield. We anonymized the identifier in this example. This identifier is required for authentication in the following requests, as it proves we are using an active session.

Awesome! With this endpoint in place, we can continue on with our second Webservice.

The Next Step

This is where it all happens. This is the part you have been waiting for. It's where the actual Twinfield requests take place. Where you can synchronize your payments from your Twinfield account and send your invoices from one system to the other.

As mentioned earlier, we need to define a second Webservice.
Because Twinfield offers different account-domains, it's important to use the correct 'cluster' when executing requests. When logged into Twinfield's portal, you can find your cluster in the URL of your browser.

The cluster used in our example is c4. All possible clusters (at the time of writing) are:

  • accounting
  • accounting2 
  • c3
  • c4 
  • c5 (German)

Note: Replace the cluster used in all examples with the correct cluster for your environment(!)

Go to Webservices and add a new Webservice. Enter the following values in the corresponding fields:

  • Name: Twinfield
  • Protocol: HTTPS
  • Host: "c4.twinfield.com" 
  • Authentication Type: None
  • Request Content-Type: Other
  • Response Content-Type: Other
  • Headers: Content-Type : text/xml 

Save the webservice and click the + next to Endpoints (0) to create the first webservice endpoint

Different operations explained

We've set the a good basis for executing our requests:

  • We added a Webservice to log in
  • We can receive a SessionID (authentication purposes)
  • We added a Webservice to execute our actual requests with

Now we need to define those requests. Twinfield offers a full-service bookkeeping/accountancy platform, which means they have a lot of different options. 

Normally, each option would have its own endpoint. To some point, this is also the case for Twinfield. However, Twinfield has 1 endpoint to send requests to, where (based on the content of the template) is decided which operation is executed. 

All webservice endpoints you're going to add in your application will be using the same Path value: "/webservices/processxml.asmx?wsdl", but each endpoint has a different template.

We'll show you one of those requests, which can be used as example. Then we'll show you how to combine the login with the request in an action. All other operations and how to use them can be found in Twinfield's documentation.

Note: This is an example. There is a chance it differs from the data required for your Twinfield environment, as each Twinfield environment can have a different purpose. 

Send invoice

This request can be used to send an invoice to your Twinfield environment. 

  • Name: Send invoice
  • Http method: POST
  • Path: "/webservices/processxml.asmx?wsdl" 
  • Request Content-Type: [inherit]
  • Response Content-Type: [inherit]
  • Template: - New -

Save the endpoint and you'll see how a new template is generated which can be accessed on the right side of the form.
We'll be using this template to define the message which is included in our request. Open and edit the template.
You can also rename it, this will make it easier to recognize. We called it Twinfield: Send invoice.

The template is still empty, so enter something like the following value:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:twin="http://www.twinfield.com/">
    <soapenv:Header>
        <twin:Header>
            <twin:SessionID>{{session_token}}</twin:SessionID>
        </twin:Header>
    </soapenv:Header>
    <soapenv:Body>
        <twin:ProcessXmlDocument>
            <twin:xmlRequest>
                <transactions>
                    <transaction destiny="final" raisewarning="false">
                        <header>
                            <office>{{twinfield_office}}</office>
                            <code>VRK</code>
                            <currency>EUR</currency>
                            <period>{{record.invoice_date | date: "%Y/%m"}}</period>
                            <date>{{record.invoice_date | date: "%Y%m%d"}}</date>
                            <duedate>{{record.expiring_date | date: "%Y%m%d"}}</duedate>
                            <invoicenumber>{{record.invoice_number}}</invoicenumber>
                        </header>
                        <lines>
                            <line id="1" type="total">
                                <dim1>dimension</dim1>
                                <dim2>{{record.debtor.twinfield_identifier}}</dim2>
                                <value>{{record.invoice_amount}}</value>
                                <debitcredit>debit</debitcredit>
                            </line>
                            <line id="2" type="detail">
                                <dim1>dimension</dim1>
                                <value>{{record.amount_open}}</value>
                                <debitcredit>credit</debitcredit>
                            </line>
                        </lines>
                    </transaction>
                </transactions>
            </twin:xmlRequest>
        </twin:ProcessXmlDocument>
    </soapenv:Body>
</soapenv:Envelope>

How this message is supposed to be built is related to your Twinfield, we can't decide for you how your Twinfield accepts the XML, so please refer to Twinfield's documentation and the settings of your environment. 

What should be the same in most templates though, is the header's SessionID. Take a look at the header:

<soapenv:Header>
        <twin:Header>
            <twin:SessionID>{{session_token}}</twin:SessionID>
        </twin:Header>
    </soapenv:Header>

In the part <twin:SessionID>, we have placed a variable. This variable should point at a value that holds a valid session token. Wether the token is saved in property on a record or is directly taken from the response of a previous event, it should contain a token.

The other variables included in the template are pointed at other values related to the invoice. For example, by using {{record.debtor.twinfield_identifier}} you can include a value of the related debtor of the invoice.

Combining requests

As we showed above, it's necessary to include the session token we received from the first request in the second request. This is done by using variables. In the response of the first request, we receive something like the following value:

<soap:Header>
        <Header xmlns="http://www.twinfield.com/">
            <SessionID>d39f2a37-xxxx-xxxx-xxxx-xxxxxxxxxxxx</SessionID>
            <AccessToken />
        </Header>
    </soap:Header>

To collect the SessionID part, we're going to use an xpath variable. We do need to replace a certain value first though, or it won't work.

  • Create an action which is going to hold the Twinfield events.
  • Add an Http request event for the Authentication.
  • Call the Response as variable login_response.
  • Add an Http request event for a second Twinfield event (in our case Send invoice).

This is pretty straightforward. In the last event, we're adding a Text expression variable to replace a value. Call it xml_replace.

replace(var:login_response, 'xmlns="http://www.twinfield.com/"', '')

With the unnecessary part replaced, we can get the required value from our XML.

Create yet another Text expression variable in this event, but call it session_token.

xpath(var:xml_replace, '//soap:Envelope/soap:Header/Header/SessionID/text()')

The value this variable will contain as result of the given expression can be used for authentication in further requests. Make sure the variable used in your template matches the name of the Text expression variable containing the xpath expression.

This construction is used for basically every requests/operation Twinfield has to offer. More information about all Twinfield requests can be found here: Twinfield Documentation

Did this answer your question?