Nav

API Reference

Introduction

By default, the Twikey API Docs demonstrate API interaction using cUrl. 
You can always select a specific language from the selection above.
copy
copy
copy
copy
copy
copy

Twikey offers a simple and safe multichannel solution to negotiate and collect recurring (or even occasional) payments using your own bank account and one or multiple payment service providers. We help you simplify your financial flows with Credit Card, SEPA Direct Debit or any other payment method by leveraging your own payment service provider or bank account.

Twikey has integrations with a lot of accounting and CRM packages. It is one of the first providers to operate on a European level for Direct Debit and can work directly with most European Banks. However, you can use the payment options of your favorite PSP to allow other customers to pay as well.

Twikey has set up an api enabling software partners, companies to integrate with their own systems. As there are numerous ways in which a business can make its customers pay, we have also provided a few high-level usage examples to get you started in no time.

Usage Guidelines

Sessions

All API requests must include a valid session token in the Authorization header. This token can be obtained using the login endpoint and remains valid for 24 hours. We recommend triggering the login call only when a request returns a 401 Unauthorized response. This approach simplifies token management by allowing your application to request a new token only when necessary.

Feeds

Status changes are much more common for agreements, invoices and transactions than for regular one-off payments. Since we don't want you to miss any event and multiple webhooks might not arrive in the correct order due to network latency, we provide "feeds" or queue's which are actually a list of events you haven't received yet.

Each data feed (or queue of events so you will) includes the last sequence number (X-LAST) that allows clients to resume from a specific point in the event stream (via the request header X-RESUME-AFTER). This mechanism is particularly useful for error recovery or when reconnecting after an interruption (and also quite helpful when testing).

Avoid having multiple clients in your network access the same feed simultaneously, as this can lead to race conditions and inconsistent data on your end. If concurrent access is necessary, consider one of the following approaches:

As security is essential for payments, Twikey has decided not to pass on details about payments in webhooks, but only to provide them in requests coming from your end. We allow specific "detail" calls about an object but since they are only a snapshot of the object at that time (and might already have changed by the time you've handled it) we've added rate-limits to avoid using them.

Webhooks vs Polling

We support 2 kinds of integrations.

With a push-fetch architecture Twikey notifies you about an event in Twikey via a webhook. You can use this webhook as trigger to retrieve all events from the feed (or queue) and update your internal systems.

Note: Webhooks serve as notifications only and must be handled accordingly. To prevent duplicate deliveries due to timeouts or retries, ensure any internal business logic is handled asynchronously.

The alternative is a pull architecture, clients periodically fetch updates from Twikey to get the latest state within Twikey. This architecture caters for situations when no incoming webhooks are allowed. However, the downside is that there is a delay between the moment an event occurs in Twikey and the moment you receive the event.

Hybrid solutions where hooks are combined with recurrent pulls are obviously also possible.

Idempotency Keys

A request can return different status codes such as 200 meaning all is well or a 400 when you know the call did not succeed. But what when it is crucial that a certain request doesn't hit our system twice, when due to some networking issue for example you'd ideally want to retry?

To provide a way to ensure each request you make is unique we support the standard idempotency header for our creation endpoints of transactions, refunds and subscriptions.

Idempotency-Key: "your unique reference"

Regardless if the request succeeded, failed or succeeded but wrong parameters were passed and we returned an error, you can only use the request once as the idempotency-key can only be used once and ensure that no duplicates are created.

We already supported idempotency implicitly for example trying to cancel the same mandate multiple times. This would have no adverse effect. From now, you will receive a response 409 (Conflict) when using the idempotency header with the same value more then once while a response is not yet available and you are trying to use the same key in your request(s).

Once we can return the response from your original request, that is returned upon using the same idempotency-key. The same body and HTTP code from your original request is returned.

Error Handling

API errors fall into two main categories:

  1. Client Errors (HTTP 400):

    These occur due to issues in the request and are communicated via a 400 Bad Request response. An ApiError header will be included with further details about the issue. The error message is localized based on the Accept-Language header. If this header is not provided, the message defaults to English.

  2. Server Errors (HTTP 500):

    These indicate an internal error on our side. While rare, they suggest an unexpected failure in the API. In most cases, our team is already aware of such incidents when they occur.

Sample use cases

The fitness scenario

Imagine you're going to the fitness where you exercise every once in a while. Since this doesn't come free you pay a monthly subscription fee. This subscription fee is a recurring payment (we'll come back to that later). But since all those exercises get you thirsty, you want to drink there too. But of course, there are hotter and colder days. So how much you drink can vary.

Regardless of what you consume, the fitness wants to get paid. But you'd like to avoid getting out your wallet when you're all sweaty. So your fitness made the great decision to use direct debits to allow you to pay. At the end of the month they add the recurring amount to the one-off's (the drinks). They send the payment request to the bank. Everyone's happy and relax.

What did the fitness do to achieve this relaxed way of working:

First of all, everything starts with a mandate. When a customer enrolls at the fitness he fills in the details for the internal bookkeeping of the fitness. The same information is sent to the prepare call. This returns a URL (representing an unsigned mandate) that opens up after entering all details in the enrollment screen. Since the fitness has an internal subscription number they want to use to track payments and have multiple subscriptions (eg. with and without personal trainer) they add both parameters in the prepare call. Making it visible in the mandate overview.

Once the customer filled in the account info and signed the mandate he's sent back to the fitness website with a "thank you" message. When a customer ask for a drink at the bar, the cash register calls the transaction endpoint with the details and amount. These are one-off transactions. Recurring transactions can either be handled the same way or if a subscription parameter was added in the prepare call, a recurring transaction is automatically added every month to get this subscription paid. At the end of each month, the file is created and sent to the bank manually via the collect call or automatically every night. Your bill is paid and the bank sends you the money. When the bank sends us back the account information, it is marked in your transaction overview that the transaction was paid. This information can be retrieved by the payment call. This call returns all new payment information since the last call. If a payment didn't succeed, you can configure what will happen next in the interface in the dunning section.

The parking app scenario

Because my customers don't like running in the rain looking for a parking meter while there's an app for that. How can I use Twikey to get my parking fees paid? First of all, you need a mandate . There are three possible options to have this mandate signed:

Use an in-app browser with the link retrieved via the prepare call Use the sign call to invite the user to sign via an sms confirmation Use the sign call adding the manual signature as a png-image in the payload Once the mandate is signed, in-app purchases can be sent from the backend of the app, collected the same way as mentioned in the fitness scenario.

The (off-line) webshop scenario

I have a shop where people come in physically but I also have a webshop. I want people who signed a mandate (either online via the above flow or physically) to be able to purchase something from the online shop without requiring them to use their credit card and if possible give them the sameconvenience in the physical shop. This way I can send them an invoice every month by only registering their purchases and collecting the payment via direct debit.

In the past, I had to send out all invoices and patiently waited for them to be paid. Since I'm not the most patient person on earth and even a bit chaotic at times, I'd rather collect the money directly from my customers. This way they can't forget to pay and I don't need to remind them to do so. How convenient this is for both my customers and I.

Authentication

Login

When using the API the login call will provide you with an session token, which is to be send upon every subsequent call (via the authorization header) in order to use the api. If enhanced security is setup, the private key allows the generation of a Time-based One-time password. View our sample code snippets on how to calculate this OTP in various languages. Please do not pass the apiKey as a query parameter in the url as it exposes it in some proxies. Instead pass it into the body of the request.

Normally this call is made on request of another call that returned a 401 (UNAUTHORIZED) indicating that there was no session token or that it expired.

copy
curl -X POST https://api.twikey.com/creditor /
-d apiToken=**API KEY**
copy
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Twikey\Api;

$httpClient = new Client();
$APIKEY = "**API_KEY**";
$twikeyclient = new Twikey($httpClient,$APIKEY);
copy
let twikeyClient:TwikeyClient = new TwikeyClient({
    apiKey: "apiKey",
    apiUrl: "https://mycompany.twikey.com/api/creditor",
    userAgent: "myApp",
});
copy
public class TwikeyAPI {
    private String apiKey = "**API_KEY**";
    
    public void logIn(){
        TwikeyClient twikeyClient = new TwikeyClient(apiKey)
            .withUserAgent("myApp");
    }
}
copy
public class TwikeyAPI {
    private String apiKey ="**API_KEY**";

    public void logIn(){
        TwikeyClient twikeyClient = new TwikeyClient(apiKey)
            .WithUserAgent("myApp");
    }
}
copy
import "github.com/twikey/twikey-api-go"

func main() {
   client := twikey.NewClient("**API_KEY**")
}
copy
import twikey

APIKEY = '**API_KEY**'
twikeyClient = twikey.TwikeyClient(APIKEY, "https://apiurl_as_found_in_twikey")
Responsecopy
{
  "Authorization": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
}

This session token has a validity of 24h.

HTTP Request

POST https://api.twikey.com/creditor

Form Parameters

NameDescriptionRequiredType
apiTokenAPI keyYesstring
otpValue calculated based on salt and private key (if enhanced security)Nolong

HTTP Response

CodeDescription
200Always, even upon failure (1)

(1) For security reasons, the http status is always 200, the regular error response-headers (ApiError/ApiErrorCode) are available. Other (regular) endpoints return a 401 upon passing an invalid session token.

Error codes

CodeDescription
err_no_loginNot logged in

Logout

Invalidates the AuthorizationToken by making a GET request to /creditor

copy
curl https://api.twikey.com/creditor \
  -H 'authorization: authorization'

HTTP Request

GET https://api.twikey.com/creditor

HTTP Response

CodeDescription
204Logged out successfully

Mandate

Invite a customer

Necessary to start with an eMandate or to create a contract. The end-result is a signed or protected shortlink that will allow the end-customer to sign a mandate or contract. The (short)link can be embedded in your website or in an email or in a paper letter. We advise to use the shortlink as the data is not exposed in the URL's. The parameters are as described in detail on the page "Create contracts".

After signing the end-customer is either presented with a thank-you page or is redirected to a (Global) Thank you page defined on the profile. This url can contain variables that are filled in depending on the outcome. See Thank you page

Requires a ct_id which can be found in the Twikey Creditor Template overview

copy
curl -X POST https://api.twikey.com/creditor/invite \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'authorization: **authorization**' \
  -d 'ct=**ct_id**' \
  -d 'l=nl' \
  -d 'email=support@twikey.com' \
  -d 'lastname=Support' \
  -d 'firstname=Twikey' \
  -d 'mobile=32479123123' \
  -d 'address=Stationstraat 43' \
  -d 'zip="9051"' \
  -d 'city=Sint Denijs Westrem' \
  -d 'country=BE'
copy
$host = "https://api.twikey.com";
$ct = **ct_id**;
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/invite");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "ct=$ct"
    ."&l=nl"
    ."&email=support%40twikey.com"
    ."&lastname=Support"
    ."&firstname=Twikey"
    ."&address=Stationstraat%2043"
    ."&zip=9051"
    ."&city=Sint%20Denijs%20Westrem"
    ."&country=BE"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
$result = json_decode($server_output);
$url = $result->{'url'} ;
curl_close ($ch);
copy

let invite = await twikeyClient.document.create({
    ct: Number(CT),
    email: "no-reply@example.com",
    firstname: "Twikey",
    lastname: "Support",
    address: "Derbystraat 43",
    city: "Gent",
    zip: "9000",
    country: "BE",
    l: 'nl',
})

console.log("Redirect to : " + invite.url)
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String ct = "**ct_id**";
    private String authorisation = null; //collected through logIn

    public void invite(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
                .add("ct", ct)
                .add("l", "nl")
                .add("email", "support@twikey.com")
                .add("firstname", "Twikey")
                .add("lastname", "Support")
                .add("address", "Stationstraat 43")
                .add("zip", "9051")
                .add("city", "Sint-Denijs-Westrem")
                .add("country", "BE")
                .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/invite")
          .post(formBody)
          .addHeader("Content-Type", "application/x-www-form-urlencoded")
          .addHeader("Authorization", authorisation)
          .addHeader("Cache-Control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct ="**ct_id**",
        authorisation = null; // collected through logIn

    public void invite(){
        RestClient client = new RestClient(host + "/creditor/invite");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter("application/x-www-form-urlencoded",
            "ct=" + ct +
            "&l=nl" +
            "&email=support%40twikey.com" +
            "&lastname=Support" +
            "&firstname=Twikey" +
            "&address=Stationstraat%2043" +
            "&zip=9051" +
            "&city=Sint%20Denijs%20Westrem" +
            "&country=BE"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "mndtId": "COREREC01",
  "url": "http://twikey.to/myComp/ToYG",
  "key": "ToYG"
}

HTTP Request

POST https://api.twikey.com/creditor/invite

Query Parameters

NameDescriptionRequiredTypeMax.
ctContract template to useYesinteger
lLanguage (en / fr / fr_fr / nl / nl_nl / de / pt / es / it)Nostring2
ibanInternational Bank Account Number of the debtorNostring35
bicBank Identifier Code of the IBANNo [*6]string11
mandateNumberMandate Identification number (if not generated) [*1]Nostring34
customerNumberThe customer number (strongly advised)Nostring50
emailEmail of the debtorNostring70
lastnameLastname of the debtorNostring50
firstnameFirstname of the debtorNostring50
mobileMobile number required for sms (International format +32499123445)Nostring50
To update the address all address fields parameters are requiredYes--
addressAddress (street + number)Nostring70
cityCity of debtorNostring50
zipZipcode of debtorNostring12
countryISO formatNostring2
companyNameThe company name (if debtor is company)Nostring140
vatnoThe enterprise number (if debtor is company)Nostring50
contractNumberThe contract number which can override the one defined in the template.Nostring35
campaignCampaign to include this url inNostring250
prefixOptional prefix to use in the url (default companyname)Nostring20
checkIf a mandate already exists, don't prepare a new one (based on email, customerNumber or mandatenumber and + template type(=ct))Noboolean
edExpiry of the link (max.12 months in the future). Epoch timestamp in millisecondsNonumber
reminderDaysSend a reminder if contract was not signed after number of daysNonumber
sendInviteSend out invite directly [*2]Noboolean/string
token(optional) token to be returned in the exit-url (lenght<100) Percent Encoded characters are not supportedNostring
requireValidationAlways start with the registration page, even with all known mandate detailsNoboolean
documentAdd a contract in base64 formatNostring
transactionAmountIn euro for a transaction via a first payment or post signature via an SDD transactionNostring
transactionMessageMessage for the transaction [*3]Nostring
transactionRefReference of the transaction [*4]Nostring
planName of the the plan. see special cases Add a plan on invite below for more information.Nostring
subscriptionStartStart date of the subscription (yyyy-mm-dd). This is also the first execution date. Required if adding a subscriptionNodate
subscriptionRecurrence1w / 1m / 2m / 3m / 6m /12m (default 1m)Nostring
subscriptionStopAfterNumber of times to execute the subscriptionNonumber
subscriptionAmountAmount for every transactionNonumber
subscriptionMessageMessage to the subscriberNostring
subscriptionRefUnique Reference of the subscription (important for updates) [*5]Nostring

[*1] The mandateNumber is mandatory if you enabled the 'Never generate a mandate reference' in the template settings under 'Options'. The mandateNumber cannot use the same prefix as your template to avoid conflicts.

[*2] sendInvite can have a boolean or a string value, both are accepted:

[*3] The transaction reference is either:

[*4] The transaction description is either:

[*5]

[*6]

Special cases:

The first payment to sign this type of document is always a payment link. Retrieve the mandate feed using a x-types header and/or the payment link feed to fetch details.

You need a Credit Card profil with the signing method 'Paypal' enabled (contact our support to enable this).
In your API request include the additional attribute methodwith value paypal

When adding a subscription to a mandate it might be interesting to use a parent plan (in order to slice and dice your subscriptions). It also allows to change the base parameters of the subscription without the need to change your code.

To add a subscription based on a plan multiple options can be used:

To pass a custom subscription on invite you need to pass the plan parameter using the name of an existing plan as value. This serves only as a base the subscription will use the values (subscriptionRecurrence, _subscriptionStart, etc..._) you passed using the subscription parameters in the request.

The subscription is created once the end customer signs.

HTTP Response

CodeDescription
200If the check option is provided and an existing collectable mandate is available it will be returned. Otherwise an url and key will be returned.
400User error if parameter is given but not valid or collectable (available in apierror header and response)

Error codes

CodeDescription
err_no_such_ctNo template found (or not active)
err_mandatenumber_requiredNo mandatenumber was given, while setting does not allow generation
err_double_mandatenumberSigned mandate with the same mandatenumber already exists
err_missing_paramsAttributes are missing while configured as mandatory
err_contract_signedSpecified contract is already signed
err_invalid_mandatenumberReference or prefix is invalid
err_bic_not_sepaBIC code does not belong to a SEPA bank
err_duplicate_refDuplicated reference
err_invalid_bicBIC code invalid
err_invalid_cocInvalid enterprise number
err_invalid_countryCountry code invalid
err_invalid_emailEmail invalid
err_invalid_ibanIBAN invalid
err_invalid_langLanguage code invalid
err_too_longThe value of a parameter is longer then allowed
err_email_disabledNo email integration is configured or all emails are disabled. Unable to send the invitation

Sign a mandate

copy
curl -X POST https://api.twikey.com/creditor/sign \
  -H 'authorization: **authorization**' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'method=sms' \
  -d '&place=Gent' \
  -d '&ct=**ct_id**' \
  -d '&iban=BE68068897250734' \
  -d '&bic=GKCCBEBB' \
  -d '&email=sms%40twikey.com' \
  -d '&lastname=Twikey' \
  -d '&firstname=SMS' \
  -d '&mobile=%2B32479123123' \
  -d '&address=Stationstraat%2043' \
  -d '&city=Gent' \
  -d '&zip=9051' \
  -d '&country=BE'
copy
$host = "https://api.twikey.com";
$ct = **ct_id**;
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/sign");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "method=sms"
    ."&place=Gent"
    ."&ct=1323"
    ."&iban=BE68068897250734"
    ."&bic=GKCCBEBB"
    ."&email=sms%40twikey.com"
    ."&lastname=Twikey"
    ."&firstname=SMS"
    ."&mobile=%2B32479123123"
    ."&address=Stationstraat%2043"
    ."&city=Gent"
    .'&zip=9051'
    ."&country=BE"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");

$server_output = curl_exec ($ch);
$result = json_decode($server_output);
$mandateId = $result->{'MndtId'} ;
curl_close ($ch);
copy

let invite = await twikeyClient.document.sign({
    method: 'itsme', // or emachtiging / idin / ....
    ct: Number(CT),
    email: "no-reply@example.com",
    firstname: "Twikey",
    lastname: "Support",
    address: "Derbystraat 43",
    city: "Gent",
    zip: "9000",
    country: "BE",
    l: 'nl',
})
copy
public class TwikeyApi{
    private string host = "https://api.twikey.com";
    private String ct = "**ct_id**";
    private String authorisation = null; //collected through logIn

    public void inviteAndSign(){
        OkHttpClient client = new OkHttpClient();

        RequestBody body = new FormBody.Builder()
            .add("ct",ct)
            .add("method", "sms")
            .add("place"="Gent")
            .add("mobile","+32479123123")
            .add("l","nl")
            .add("email","support%40twikey.com")
            .add("lastname","Support")
            .add("firstname","Twikey")
            .add("address","Stationstraat%2043")
            .add("zip","9051")
            .add("city","Sint Denijs Westrem")
            .add("country","BE")
            .build();
        Request request = new Request.Builder()
          .url(host + "/creditor/sign")
          .post(body)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct ="**ct_id**",
        authorisation = null; // collected through logIn

    public void inviteAndSign(){
        RestClient client = new RestClient(host + "/creditor/sign");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter("application/x-www-form-urlencoded",
            "method=sms" +
            "&place=Gent"
            "&ct="+ ct +
            "&iban=BE68068897250734" +
            "&bic=GKCCBEBB" +
            "&email=sms%40twikey.com" +
            "&lastname=Twikey" +
            "&firstname=SMS" +
            "&mobile=%2B32479123123" +
            "&address=Stationstraat%2043" +
            "&city=Gent" +
            "&zip=9051" +
            "&country=BE" +
            , ParameterType.RequestBody);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "MndtId": "MyMandateId"
}
Response with sign method Itsmecopy
{
  "MndtId": "MNDT123",
  "url": "https://e2emerchant.itsme.be/oidc/authorization?response_type=code&client_id=M48..."
}

Create a contract with an invitation/signature directly via API. Note that this call can require different parameters depending on the method of signature. All parameters are described in Create contracts When enabled for your contract it is possible to negotiate mandates with their signature directly via API. Depending on the method the set of required parameters and/or handling may differ. Methods currently supported :

Credit cards:

Some psp's have limited support for some more exotic methods, please see the integration page in Twikey to verify if your method is supported.

HTTP Request

POST https://api.twikey.com/creditor/sign

Query Parameters

Same parameters as the invite call +

NameDescriptionRequiredTypeMax.
methodMethod to sign (sms/digisign/import/itsme/emachtiging/paper,...)Yesstring
digsigWet signature (PNG image encoded as base64) required if method is digisignNostring
keyshortcode from the invite url. Use this parameter instead of 'mandateNumber' to directly sign a prepared mandate.Nostring36
bicRequired for methods emachtiging and iDINNostring11
signDateDate of signature (xsd:dateTime), sms uses date of replyNostring
placePlace of signatureNostring
bankSignatureFor B2B only, require bank validation if set on false. The value is true by default.Noboolean

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_such_ctNo template found (or not active)
err_mandatenumber_requiredNo mandatenumber was given, while setting does not allow generation
err_double_mandatenumberSigned mandate with the same mandatenumber already exists
err_missing_paramsAttributes are missing while configured as mandatory
err_invalid_signatureNo valid method was provided
err_bic_not_sepaBIC code is not SEPA
err_contract_signedContract is already signed
err_not_authorisedsign method not authorized
err_invalid_bicBIC code is invalid
err_invalid_emailEmail is invalid
err_invalid_ibanIBAN is invalid
err_invalid_mobileMobile number format is invalid
smsErrorSendingError sending SMS
smsFixedPhone number is fixed instead of mobile
smsPendingContractMobile number currently in use and awaiting response from customer
err_contact_supportConfiguration problem, contact support

Mandate Feed

copy
curl https://api.twikey.com/creditor/mandate \
  -H 'authorization: **authorization**' \
copy
$twikey->document->feed(new class implements DocumentCallback {
   function handleNew($update)
   {
       print("New " . $update->Mndt->MndtId . ' @ '. $update->EvtTime . "\n");
   }

   function handleUpdate($update)
   {
       $rsn = $update->AmdmntRsn->Rsn;
       print("Update: " . $update->Mndt->MndtId . ' -> '. $rsn . ' @ '. $update->EvtTime . "\n");
   }

   function handleCancel($update)
   {
       $rsn = $update->CxlRsn->Rsn;
       print("Cancel: " . $update->OrgnlMndtId . ' -> '. $rsn . ' @ '. $update->EvtTime . "\n");
   }
}
);
copy
import twikey

class MyDocumentFeed(twikey.DocumentFeed):
    def newDocument(self, doc, evt_time):
        print("new ", doc["MndtId"])

    def updatedDocument(self, original_mandate_number, doc, reason, evt_time):
        print("update ", doc["MndtId"], "b/c", reason["Rsn"])

    def cancelDocument(self, doc_number, reason, evt_time):
        print("cancelled ", doc_number, "b/c", reason["Rsn"])

twikey.TwikeyClient.document.feed(MyDocumentFeed())
copy
foreach(var mandateUpdate in await twikeyClient.Document.FeedAsync())
{
    if(mandateUpdate.IsNew())
    {
        Console.WriteLine("New mandate: " + JsonConvert.SerializeObject(mandateUpdate, Formatting.Indented));
    }
    else if(mandateUpdate.IsUpdated())
    {
        Console.WriteLine("Updated mandate: " + JsonConvert.SerializeObject(mandateUpdate, Formatting.Indented));
    }
    else if(mandateUpdate.IsCancelled())
    {
        Console.WriteLine("Cancelled mandate: " + JsonConvert.SerializeObject(mandateUpdate, Formatting.Indented));
    }
}
copy
err := c.DocumentFeed(context.Background(), 
    func(mandate *Mndt, eventTime string, eventId int64) {
        fmt.println("Document created   ", mandate.MndtId, " @ ", eventTime)
    }, func(originalMandateNumber string, mandate *Mndt, reason *AmdmntRsn, eventTime string, eventId int64) {
        fmt.println("Document updated   ", originalMandateNumber, reason.Rsn, " @ ", eventTime)
    }, func(mandateNumber string, reason *CxlRsn, eventTime string, eventId int64) {
        fmt.println("Document cancelled ", mandateNumber, reason.Rsn, " @ ", eventTime)
    })
copy

twikeyClient.document().feed(new DocumentCallback() {
    @Override
    public void newDocument(JSONObject newMandate) {
        System.out.println("New mandate: "+newMandate);
    }

    @Override
    public void updatedDocument(JSONObject updatedMandate) {
        System.out.println("Updated mandate: "+updatedMandate);
    }

    @Override
    public void cancelledDocument(JSONObject cancelledMandate) {
        System.out.println("Cancelled mandate: "+cancelledMandate);
    }
})
copy
const feed = client.document.feed();
for await (const document of feed) {
   if (document.IsNew) {
      console.log("New mandate: ",newMandate)
   }
   if (document.IsUpdated) {
      console.log("Updated mandate: ",newMandate)
   }
   if (document.IsCancelled) {
      console.log("Cancelled mandate: ",newMandate)
   }
}
Responsecopy
{
  "GrpHdr": {
    "CreDtTm": "2021-04-09T12:49:46Z"
  },
  "Messages": [
    {
      "Mndt": {
        "MndtId": "B2B38434",
        "LclInstrm": "B2B",
        "Ocrncs": {
          "SeqTp": "RCUR",
          "Frqcy": "ADHO",
          "Drtn": {
            "FrDt": "2021-04-09"
          }
        },
        "CdtrSchmeId": "BE81ZZZ1234567891",
        "Cdtr": {
          "Nm": "Merchant Company",
          "PstlAdr": {
            "AdrLine": "Companystreet 100",
            "PstCd": "1000",
            "TwnNm": "Brussel",
            "Ctry": "BE"
          },
          "Id": "BE0123456789",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "merchant.email@example.com"
          }
        },
        "Dbtr": {
          "Nm": "Twikey NV",
          "PstlAdr": {
            "AdrLine": "Stationstraat 43",
            "PstCd": "9051",
            "TwnNm": "Gent",
            "Ctry": "BE"
          },
          "Id": "BE0533800797",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "support@twikey.com",
            "Othr": "custNumber001"
          }
        },
        "DbtrAcct": "BE31798258915655",
        "DbtrAgt": {
          "FinInstnId": {
            "BICFI": "GKCCBEBB",
            "Nm": "BELFIUS BANK"
          }
        },
        "RfrdDoc": "Terms and Conditions",
        "SplmtryData": [
          {
            "Key": "SignerMethod#0",
            "Value": "maestro"
          },
          {
            "Key": "Signer#0",
            "Value": "Mock Maestro"
          },
          {
            "Key": "SignerPlace#0",
            "Value": "Ghent"
          },
          {
            "Key": "SignerDate#0",
            "Value": "2021-04-09T13:18:19Z"
          }
        ]
      },
      "EvtTime": "2021-04-09T13:19:19Z"
    },
    {
      "Mndt": {
        "MndtId": "NL-B2B60",
        "LclInstrm": "B2B",
        "Ocrncs": {
          "SeqTp": "RCUR",
          "Frqcy": "ADHO",
          "Drtn": {
            "FrDt": "2021-04-09"
          }
        },
        "MaxAmt": "1000",
        "CdtrSchmeId": "BE81ZZZ1234567891",
        "Cdtr": {
          "Nm": "Merchant Company",
          "PstlAdr": {
            "AdrLine": "Companystreet 100",
            "PstCd": "1000",
            "TwnNm": "Brussel",
            "Ctry": "BE"
          },
          "Id": "BE123456789",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "merchant.email@example.com"
          }
        },
        "Dbtr": {
          "Nm": "Twikey BV",
          "PstlAdr": {
            "AdrLine": "Bisschop de Vetplein 7",
            "PstCd": "5126CA",
            "TwnNm": "Gilze",
            "Ctry": "NL"
          },
          "Id": "65772989",
          "CtryOfRes": "NL",
          "CtctDtls": {
            "EmailAdr": "support@twikey.com",
            "Othr": "custNumber002"
          }
        },
        "DbtrAcct": "BE31798258923546",
        "DbtrAgt": {
          "FinInstnId": {
            "BICFI": "GKCCBEBB",
            "Nm": "BELFIUS BANK"
          }
        },
        "RfrdDoc": "General terms and conditions",
        "SplmtryData": [
          {
            "Key": "SignerMethod#0",
            "Value": "mock"
          },
          {
            "Key": "Signer#0",
            "Value": "Twikey Mock Signer"
          },
          {
            "Key": "SignerPlace#0",
            "Value": "Ghent"
          },
          {
            "Key": "SignerDate#0",
            "Value": "2021-04-09T12:49:42Z"
          }
        ]
      },
      "EvtTime": "2021-04-09T13:19:34Z"
    },
    {
      "AmdmntRsn": {
        "Orgtr": {
          "Nm": "Twikey NV",
          "PstlAdr": {
            "AdrLine": "Stationstraat 43",
            "PstCd": "9000",
            "TwnNm": "Gent",
            "Ctry": "BE"
          },
          "Id": "BE0533800797",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "support@twikey.com"
          }
        },
        "Rsn": "_T50"
      },
      "Mndt": {
        "MndtId": "CF2498",
        "LclInstrm": "CORE",
        "Ocrncs": {
          "SeqTp": "RCUR",
          "Frqcy": "ADHO",
          "Drtn": {
            "FrDt": "2021-04-09"
          }
        },
        "CdtrSchmeId": "BE81ZZZ1234567891",
        "Cdtr": {
          "Nm": "Merchant Company",
          "PstlAdr": {
            "AdrLine": "Companystreet 100",
            "PstCd": "1000",
            "TwnNm": "Brussel",
            "Ctry": "BE"
          },
          "Id": "BE123456789",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "merchant.email@example.com"
          }
        },
        "Dbtr": {
          "Nm": "Company NV",
          "PstlAdr": {
            "AdrLine": "streetname 100",
            "PstCd": "1000",
            "TwnNm": "Brussel",
            "Ctry": "BE"
          },
          "Id": "BE0154521254",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "customer.email@example.com",
            "Othr": "custNumber00125"
          }
        },
        "DbtrAcct": "BE12123356223353",
        "DbtrAgt": {
          "FinInstnId": {
            "BICFI": "GKCCBEBB",
            "Nm": "BELFIUS BANK"
          }
        },
        "RfrdDoc": "HvvGeWz627a",
        "SplmtryData": [
          {
            "Key": "SignerMethod#0",
            "Value": "print"
          },
          {
            "Key": "Signer#0",
            "Value": "customer.email@example.com"
          },
          {
            "Key": "SignerPlace#0",
            "Value": "(Imported)"
          },
          {
            "Key": "SignerDate#0",
            "Value": "2021-04-09T10:03:44Z"
          }
        ]
      },
      "OrgnlMndtId": "CF2498",
      "CdtrSchmeId": "BE81ZZZ1234567891",
      "EvtTime": "2021-04-09T13:26:53Z"
    },
    {
      "CxlRsn": {
        "Orgtr": {
          "Nm": "Twikey NV",
          "PstlAdr": {
            "AdrLine": "Stationstraat 43",
            "PstCd": "9000",
            "TwnNm": "Gent",
            "Ctry": "BE"
          },
          "Id": "BE0533800797",
          "CtryOfRes": "BE",
          "CtctDtls": {
            "EmailAdr": "support@twikey.com"
          }
        },
        "Rsn": "Custom cancel reason"
      },
      "OrgnlMndtId": "CF2506",
      "CdtrSchmeId": "BE81ZZZ1234567891",
      "EvtTime": "2021-04-09T13:30:51Z"
    }
  ]
}

Returns a List of all updated mandates (new, changed or cancelled) since the last call. From the moment there are changes (eg. a new contract/mandate or an update of an existing contract) this call provides all related information to the creditor. The service is initiated by the creditor and provides all MRI information (and extra metadata) to the creditor. This call can either be triggered by a callback once a change was made or periodically when no callback can be made. This information can serve multiple purposes:

In order too avoid polling, a webhooks can be setup to notify the client when new information is available. This hook can be configured in the Settings > API.

There are 3 possible updates.

Amendments and cancellation:

Messages - SplmtryData

Each Message consists of a Mndt object containing all basic information such as merchant information (Cdtr), the customer information (Dbtr), mandate details, ..

The SplmtryData contains all non-structured data: custom attributes, plans, signer(s), ..
By default all attributes are returned, but this can be changed using the include parameter.

See Query Parameters for all possible values.

Messages - Seq

For each Message we also return the event date and time. This can be extended by the event id.
The event id is a sequence number - but is not sequential necessarily in your feed - used as identifier for the message.

To return the event identifier you can use the include parameter: include=seq
When you use include parameters, only those are returned.
See Query Parameters for all values.

Read the high level structure document for a more in depth explanation.

Signed, Updated, Cancelled

Signed

A signed mandate will be returned in the feed including all the mandate, creditor, debtor details and the supplementary data. Supplementary data returns several keys: Each person that signed the document will be returned for each key using consecutive numbers #0, #1, .. .

This is returned in the "SplmtryData[key|value]" array.

Updated

When a document is updated, it is returned in the feed as amendment with initiator and reason:

The "Rsn" value informs you about the change, this can be information on the mandate that was updated or information about the mandate state. Examples:

Cancelled

When a mandate was cancelled, this is returned as Cancelled reason with initiator and reason:

The "Rsn" value can be a custom reason the user, bank or debtor entered on cancellation. This can also contain the reason of cancellation in case of automated dunning steps. Examples:

HTTP Request

GET https://api.twikey.com/creditor/mandate

Headers

Header nameDescription
X-RESUME-AFTERResume the feed after a specific sequence id (requires the parameter include=seq)
X-TYPESTypes of contracts to include (default = CORE,B2B,CREDITCARD). Main exception is CONTRACT

Query Parameters

To reduce the feed and only return information you need, you can opt to use the include parameters.
Example: GET /creditor/mandate?include=mandate&include=signature

NameReturned parameters
include=mandateTemplateId, MandateName, custom attributes
include=personFirstName, LastName, Language
include=signatureSignerMethod#0, Signer#0, SignerPlace#0, SignerDate#0
include=planPlan
include=trackerTRACKER (shortcode used for invite)
include=seqSeq (returned in the Mndt object)
include=cancelled_mandatereturns additional customer information on a cancel feed (email and customerNumber)
include=paidamountreturn the amount paid when signing the mandate (returned in the splmtryData object)

HTTP Response

CodeDescription
200The request has succeeded

Error Codes

CodeDescription
err_call_in_progressRequest already in progress by another client

Mandate query

Create a search query to return all contracts for a specific iban, customer or a combination of different query parameters.
The response is limited to a set of 500 contracts per page. The response returns the contracts found based on your search query.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/mandate/query?iban=BE21798857497403 \
  -H 'authorization: **authorization**' \
copy
$host = "https://api.twikey.com";
$ct = **ct_id**;$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/mandate/query?iban=BE21798857497403");
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");

$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/mandate/query?iban=BE21798857497403',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void updateFeed(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .url(host + "/creditor/mandate/query?iban=BE21798857497403")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void updateFeed(){
        RestClient client = new RestClient(host + "/creditor/mandate/query?iban=BE21798857497403");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "Contracts": [
    {
      "id": 3291639,
      "type": "CORE",
      "state": "SIGNED",
      "suspended": false,
      "pdfAvailable": true,
      "mandateNumber": "MNDT123",
      "contractNumber": "General Terms",
      "ct": 2223,
      "signDate": "2024-03-01T15:02:45Z",
      "iban": "BE21798857497403",
      "bic": "GKCCBEBB",
      "attributes": {
        "vehicle": null,
        "km": null
      }
    },
    {
      "id": 3291538,
      "type": "CORE",
      "state": "SIGNED",
      "suspended": true,
      "pdfAvailable": true,
      "mandateNumber": "MNDT456",
      "contractNumber": "General Terms",
      "ct": 2223,
      "signDate": "2024-03-01T13:44:39Z",
      "iban": "BE21798857497403",
      "bic": "GKCCBEBB",
      "attributes": {
        "vehicle": "Sedan",
        "km": "12000"
      }
    }
  ],
  "_links": {
    "self": "/creditor/mandate/query?page=0"
  }
}

HTTP Request

GET https://api.twikey.com/creditor/mandate/query?parameter=value

Query Parameters

At least one of iban, customerNumber or emailis required. state is optional. A combination of these parameters is possible.

NameDescriptionRequiredTypeMax.
ibanThe IBAN of the contractYesstring35
customerNumberThe customer numberYesstring50
emailThe email address of the customerYesstring70
stateoptional, return only contracts in a specific state (SIGNED by default. Value in uppercase)Nostring
pagePagination is returned at the end of the response, include 'page' with a page number to go to the next or previous set of resultsNonumber

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Error codes

CodeDescription
err_not_foundNo contracts found with the given parameter(s) value
err_missing_paramsMissing parameters or incorrect parameter value passed.

Cancel agreements

Agreements can be cancelled by either debtor or creditor, this can be done via the website or the api. However, there may be circumstances in which the creditor receives the cancel not through Twikey. Our advise is to communicate cancellations to Twikey. That way all databases are up to date and in some cases the cancellation is also forwarded to the debtor bank. The creditor, the creditor bank or the debtor bank, can initiate this request. Afterwards the update is distributed to all parties.

copy
curl -X DELETE https://api.twikey.com/creditor/mandate?mndtId123&rsn=test \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$ct = **ct_id**;$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/mandate".
    "?mndtId=mndtId123&rsn=some%20reason"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");
curl_setOpt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/mandate?mndtId=**mdntId**&rsn=some%20reason',
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void cancelMandate(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .url(host + "/creditor/mandate?mndtId=**mndtId&rsn=some%20reason")
          .delete(null)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void cancelMandate(){
        RestClient client = new RestClient(host + "/creditor/mandate" +
            "?mndtId=mndtId123" +"
            &rsn=some%20reason"
        );
        RestRequest request = new RestRequest(Method.DELETE);
        request.AddHeader("authorization", authorisation);
        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

DELETE https://api.twikey.com/creditor/mandate

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35
rsnReason of cancellation (Can be R-Message)Yesstring200
notifyNotify the customer by email when trueNoboolean

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo mndtId was provided or invalid mndtId supplied
err_provide_reasonPlease provide reason
err_invalid_stateContract state does not allow a cancel (eg. already cancelled)
err_invalid_cancel_forbiddenDue to the mandate cancellation strategy of the profile, open transactions exist for this mandate

Fetch mandate details

Retrieve details of a specific mandate. Since the structure of the mandate is the same as in the update feed but doesn't include details about state, 2 extra headers are added. Though this is perfect for one-offs, for updates we recommend using the feed.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/mandate/detail?mndtId=mndtId123 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/mandate/detail" . "
    ?mndtId=mndtId123"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");
curl_setOpt($ch, CURLOPT_CUSTOMREQUEST, "GET");
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
const details = await client.document.detail(importedMandate);
console.log(details);
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void getMandateDetail(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .get()
          .url(host +
            "/creditor/mandate/detail" +
            "?mndtId=mndtId123"
          )
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void getMandateDetail(){
        RestClient client = new RestClient(host +
            "/creditor/mandate/detail" +
            "?mndtId=mndtId123"
        );
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "Mndt": {
    "MndtId": "MNDT8380",
    "LclInstrm": "CORE",
    "Ocrncs": {
      "SeqTp": "RCUR",
      "Frqcy": "ADHO",
      "Drtn": {
        "FrDt": "2022-09-28"
      }
    },
    "CdtrSchmeId": "BE81ZZZ1234567891",
    "Cdtr": {
      "Nm": "Merchant Company",
      "PstlAdr": {
        "AdrLine": "Companystreet  51N",
        "PstCd": "9000",
        "TwnNm": "Gent",
        "Ctry": "BE"
          },
      "Id": "BE0123456789",
      "CtryOfRes": "BE",
      "CtctDtls": {
        "EmailAdr": "merchant.email@example.com"
      }
    },
    "Dbtr": {
      "Nm": "Luke Devon",
      "PstlAdr": {
        "AdrLine": "Stationstraat 43",
        "PstCd": "9051",
        "TwnNm": "Gent",
        "Ctry": "BE"
      },
      "CtryOfRes": "BE",
      "CtctDtls": {
        "EmailAdr": "debtor@example.com",
        "Othr": "custNumber001"
      }
    },
    "DbtrAcct": "BE31798258915655",
    "DbtrAgt": {
      "FinInstnId": {
        "BICFI": "GKCCBEBB",
        "Nm": "BELFIUS BANK"
      }
    },
    "RfrdDoc": "Terms and Conditions",
    "SplmtryData": [
      {
        "Key": "TRACKER",
        "Value": "rWFq"
      },
      {
        "Key": "MandateName",
        "Value": "Core Mandate"
      },
      {
        "Key": "TemplateId",
        "Value": 2223
      },
      {
        "Key": "amount",
        "Value": "10"
      },
      {
        "Key": "licenseplate",
        "Value": "1-AAA-001"
      },
      {
        "Key": "plan",
        "Value": null
      },
      {
        "Key": "FirstName",
        "Value": "Luke"
      },
      {
        "Key": "LastName",
        "Value": "Devon"
      },
      {
        "Key": "Language",
        "Value": "nl"
      },
      {
        "Key": "SignerMethod#0",
        "Value": "print"
      },
      {
        "Key": "Signer#0",
        "Value": "Luke Devon"
      },
      {
        "Key": "SignerPlace#0",
        "Value": "(Imported)"
      },
      {
        "Key": "SignerDate#0",
        "Value": "2022-09-28T13:42:14Z"
      }
    ]
  }
}

HTTP Request

GET https://api.twikey.com/creditor/mandate/detail

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35
forceAlso include non-signed statesNoboolean

Response

CodeDescription
200Details of the mandate (see /creditor/mandate)
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Header Response

HeaderDescription
X-STATEState of the mandate
X-COLLECTABLEWhether this mandate can be used for collections (true/false)

Possible states

StateDescription
PREPAREDUser created the mandate, but the mandate is not signed yet
SIGNEDClient signed the mandate
EXPIREDMandate is beyond it's final date
CANCELLEDMandate has been revoked
SIGNED_PENDING_DEBTOR_BANKonly in case of B2B - debtor bank needs to validate the mandate
REFUSED_BY_DEBTOR_BANKonly in case of B2B - signed but debtor bank refused the mandate
REQUIRES_MORE_SIGNATURESmandate needs to be signed by a 2nd person - option needs to be activated on template level
PRINTonly in case of B2B - mandate has been printed by debtor
PENDING_MERCHANT_APPROVALmandate has been uploaded by debtor but needs to be validated by merchant

Possible events

EventDescription
Canceldepending on the state of a mandate
Accepted Bankdepending on the state of a mandate
Reject Bankdepending on the state of a mandate
Signdepending on the state of a mandate
Printdepending on the state of a mandate
Uploadeddepending on the state of a mandate
Debtor Uploaddepending on the state of a mandate
Expireddepending on the state of a mandate

Error codes

CodeDescription
err_no_contractNo contract was selected or invalid mndtId supplied
err_no_contractNo contract was found
err_invalid_stateContract was not signed (ignored when force=true)

Update mandate details

You can change details of a mandate. Note: Include email only when changing it's value. bic code is generated automatically based on iban if not included. Custom attributes that are defined in the template can also be updated.

Note: Company name and language are always updated on the mandate itself, not the owner. Note: Mobile number and email are always updated on both the mandate and customer.

copy
curl POST 'https://api.twikey.com/creditor/mandate/update' \
--h 'Authorization: ....' \
--d 'mndtId=MDT123' \
--d 'address=Stationstraat 43' \
--d 'zip=9000' \
--d 'city=Gent' \
--d 'country=BE' \
--d 'mobile=+32 483 115862' \
--d 'iban=BE75 0509 9307 0051' \
--d 'bic=BRUBBEB'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/mandate/update',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => 'mndtId=MDT123&address=Stationstraat%2043&zip=9000&city=Gent&country=BE&iban=BE32%201234%201234%201234&bic=BRUBBEB',
  CURLOPT_HTTPHEADER => array(
    'Authorization: ....'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
myHeaders.append("Authorization", "....");

var urlencoded = new URLSearchParams();
urlencoded.append("mndtId", "MDT123");
urlencoded.append("address", "Stationstraat 43");
urlencoded.append("zip", "9000");
urlencoded.append("city", "Gent");
urlencoded.append("country", "BE");
urlencoded.append("iban", "BE32 1234 1234 1234");
urlencoded.append("bic", "BRUBBEB");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/mandate/update", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "mndtId=MDT123&address=Stationstraat 43&zip=9000&city=Gent&country=BE&iban=BE32 1234 1234 1234&bic=BRUBBEB");
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/mandate/update")
  .method("POST", body)
  .addHeader("Authorization", "....")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/mandate/update");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "....");
request.AddParameter("mndtId", "MDT123");
request.AddParameter("address", "Stationstraat 43");
request.AddParameter("zip", "9000");
request.AddParameter("city", "Gent");
request.AddParameter("country", "BE");
request.AddParameter("iban", "BE32 1234 1234 1234");
request.AddParameter("bic", "BRUBBEB");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

HTTP Request

POST https://api.twikey.com/creditor/mandate/update

Query Parameters

B2B type mandate: iban cannot be updated once the document is signed.

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35
ctMove the document to a different template ID (of the same type)Nonumber
stateactive or passive (activated or suspend mandate)NoString7
mobileCustomer's mobile number to avoid errors use E.164 formatNoString50
ibanDebtor's IBANNoString35
bicDebtor's BIC codeNoString11
emailemail address of debtorNoString70
firstnameFirstname of the debtorNoString50
lastnameLastname of the debtorNoString50
companyNameCompany name on the mandateNoString140
vatnoThe enterprise number (can only be changed if companyName is changed)NoString50
customerNumberThe customer number (can be added, updated or used to move a mandate)Nostring50
llanguage on the mandateNoString2
To update the address all fields below are requiredYes--
addressAddress (street + number)NoString70
cityCity of debtorNoString50
zipZipcode of debtorNoString12
countryISO formatNoString2

HTTP Response

CodeDescription
204The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo contract was found
err_invalid_countryInvalid country code or not in ISO format (2 letters)
err_invalid_statemandate state is invalid: some query parameters are invalid or not allowed in the current state.

Move a mandate

It is possible to move the mandate to another customer (owner). Include the customer number of the target in the request.

Customer access

You may want to give your customer access to the mandate details without actually requiring him to get a Twikey account. You can do this by using this call. This call returns a url that you can redirect the user to for a particular mandate.

copy
curl -X POST https://api.twikey.com/creditor/customeraccess \
  -H 'authorization: **authorization**' \
  -d 'mndtId=mndtId123'
copy
$host = "https://api.twikey.com";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/customeraccess");
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");
curl_setOpt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setOpt($ch, CURLOPT_POSTFIELDS, array(
    "mndtId" =>  "mdntId123"
);
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
?>
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/customeraccess',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        body:{
            'mndtId': 'mndtId123'
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void updateMandate(){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
                        .add("mndtId", "mndtId123")
                        .build();
        Request request = new Request.Builder()
          .post(body)
          .url(host + "/creditor/customeraccess")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void updateMandate(){
        RestClient client = new RestClient(host + "/creditor/customeraccess);
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("authorization", authorisation);
        request.AddParameter("application/x-www-form-urlencoded",
            "mndtId=mndtId123" ,
            ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "token": "A4DE98FD091....",
    "url": "https://merchant.twikey.com/p/customeraccess?token=A4DE98FD0915F"
}

HTTP Request

POST https://api.twikey.com/creditor/customeraccess

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35

HTTP Response

CodeDescription
200Request succeeded
400Bad Request

Error Codes

NameDescription
err_no_contractNo contract was found

Retrieve pdf

Retrieve pdf of a mandate

copy
curl https://api.twikey.com/creditor/mandate/pdf?mndtId123 \
  -H 'authorization: **authorization**'

# Example for downloading the content straight to a file
curl -v -X GET https://api.twikey.com/creditor/mandate/pdf?mndtId=mndtId123 -H 'authorization: **authorization**' --output test.pdf
copy
$host = "https://api.twikey.com";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/mandate/pdf?mndtId=mndtId123");
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");
curl_setOpt($ch, CURLOPT_CUSTOMREQUEST, "GET");
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/mandate/pdf?mndtId=mdntId123',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void retrievePdf(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .get()
          .url(host + "/creditor/mandate/pdf?mndtId=mndtId123")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void retrievePdf(){
        RestClient client = new RestClient(
            host +
            "/creditor/mandate/pdf" +
            "?mndtId=mndtId123"
        );
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        request.AddParameter("application/x-www-form-urlencoded",
            "mandate.pdf",
            ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

GET https://api.twikey.com/creditor/mandate/pdf

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35

HTTP Response

CodeDescription
200PDF is available in the body of the response
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo contract was found

Upload pdf

Import existing mandate pdf (eg. scan of a printed document) After import the mandate is set on signed state. For B2B mandates the parameter bankSignature determines if it will be offered to the bank (Twikey affiliated banks only)

copy
curl -X POST https://api.twikey.com/creditor/mandate/pdf \
  -H 'authorization: **authorization**' \
  -d 'mndtId=mndtId123' \
  -d @mndtId123.pdf
copy
$host = "https://api.twikey.com";

$post = array(
    "file_box"=>"@/path/to/mndtId123.pdf",
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/mandate/pdf?mndtId=mndtId123");
curl_setOpt($ch, CURLOPT_HTTPHEADER, array(
    "authorization: $authorization",
    "Content-Type: application/pdf',
    "Content-Length: ' . strlen($post))
);
curl_setOpt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setOpt($ch, CURLOPT_POSTFIELDS, $post);
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null; //collected through login

var requestUrl = 'path/to/import.json';
var request = new XMLHttpRequest();
request.open('GET', requestUrl);
request.responseType = 'json';

request.send();
request.onload = function (){
    var options = {
        host: host,
        port: '443',
        path: '/creditor/mandate/pdf?mndtId=mndtId123',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        data: request.response
    };
    var req = https.request(options, function (res) {
        console.log(res);
    });
};
copy
import java.io.FileReader;
import java.io.BufferedReader;
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void uploadPdf(){
        String pdf;
        try{
           FileReader fr = new FileReader("/path/to/mndtId123.pdf");
           BufferedReader br = new BufferedReader(fr);
           String s;
           while((s = br.readLine()) != null) {
               pdf+=s;
           }
           fr.close();
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .post(pdf)
          .url(host + "/creditor/mandate/pdf?mndtId=mndtId123")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
        } catch (FileNotFoundException e){
            System.out.printLn(e);
        }
    }
}
copy
using System.IO;
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    String pdf = file.ReadAllText("\path\to\mndtid123.pdf");

    public void uploadPdf(){
        RestClient client = new RestClient(
            host +
            "/creditor/mandate/pdf" +
            "?mndtId=mndtId123"
        );
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("authorization", authorisation);
        request.AddParameter("application/x-www-form-urlencoded",
            pdf,
            ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

POST https://api.twikey.com/creditor/mandate/pdf

Query Parameters

NameDescriptionRequiredTypeMax
mndtIdMandate ReferenceYesstring35
bankSignatureIncludes the bank signature (true by default)Nostring

HTTP Response

CodeDescription
200Import of the pdf was done
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractContract not found
err_invalid_ibanno (valid) iban on the mandate

Retrieve legal terms

copy
curl https://api.twikey.com/creditor/legal?locale=nl_BE \
  -H 'authorization: authorization'
copy
$host = "https://api.twikey.com";
$authorisation = null; // collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/legal?locale=nl_BE");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    host = "api.twikey.com",
    authorization = null, // collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/legal?locale=nl_BE',
        headers: {
            'Content-Type': 'application/json'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/json");

        Request request = new Request.Builder()
          .url(host + "/creditor/legal?locale=nl_BE")
          .addHeader("content-type", "application/json")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/legal?locale=nl_BE");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/json");
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "tcUrl": "https://www.beta.twikey.com/nl/tc.html",
    "bySigning": "Door ondertekening van dit mandaatformulier ...",
    "rightsCore": "U kunt een Europese domiciliëring laten terugbetalen. Vraag ...",
    "rightsB2b": "Dit mandaat is uitsluitend bedoeld voor betalingen tussen bedrijven. U hebt ...",
    "infoCorrect": ""
}

Parameters

NameDescriptionRequiredType
localethe locale (fr, fr_FR, fr_BE, de, nl, nl_BE, nl_NL, es, pt, it)no (defaults to en)string

Responses

CodeDescription
200The request has succeeded

Subscriptions

Add a subscription

A subscription (previously called plan) can be added on an agreement. This means than when the subscription is run a new transaction will be created using the defined schedule. If you opted for the automatic sending this will be collected as soon as the bank permits it.

request without and with a plan:

copy
curl -X POST https://api.twikey.com/creditor/subscription \
  -H 'authorization: **authorization**' \
  -d 'mndtId=TEST03'\
  -d 'msg=Monthly subscription'\
  -d 'ref=MyRef'\
  -d 'amount=12.00'\
  -d 'recurrence=1m'\
  -d 'start=2022-11-29'
  
curl -X POST https://api.twikey.com/creditor/subscription \
  -H 'authorization: **authorization**' \
  -d 'mndtId=TEST03'\
  -d 'ref=MyRef'\
  -d 'plan=myplan'\
  -d 'start=2022-11-29'

HTTP Request

POST https://api.twikey.com/creditor/subscription

Responsecopy
{
    "id": 10,
    "state": "active",
    "amount": 12.0,
    "message": "Monthly subscription",
    "ref": "MyRef",
    "plan": 0,
    "runs": 0,
    "stopAfter": 5,
    "start": "2022-11-29",
    "next": "2022-12-01",
    "recurrence": "1m",
    "mndtId": "TEST03"
}

Request Headers

NameDescriptiontypemax. length
Idempotency-KeyUnique key usable only once per request every 24hrsstring64

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35
messageMessage to the subscriberYesstring140
planName of the base planNostring
refReference of the subscription (important for updates) [*1]Nostring
amountAmount of the transactionYesnumber
stopAfterNumber of times to executeNonumber
recurrence1w / 1m / 2m / 3m / 4m / 6m /12m (default 1m)Nostring
startStart of subscription eg. 2022-11-01 (future date only)Yesdate

[*1]: The reference is converted to uppercase and can't contain any spaces.

HTTP Response

CodeDescription
200The server has fulfilled the request
400User error if parameter is given but not valid (available in apierror header and response)
409Conflict. Idempotency-key already used

Error codes

CodeDescription
err_no_contractNo contract was found
err_invalid_dateInvalid start date
err_plan_rangeInvalid stopAfter value
err_msg_missingMessage not supplied
err_invalid_amountInvalid amount
err_invalid_paramsSome parameter is invalid, see the extra in the response body for more information.
err_duplicate_refIdempotency-key was already used in the last 24hrs.

Update a subscription

Sometimes a subscription needs to be updated. This endpoint allows the update by using the previously passed reference for a specific agreement.
The update subscription and patch subscription are similar requests, the difference be that with the post you can replace a subscription (cancel current and start new) the patch can't replace a subscription.

copy
curl -X POST https://api.twikey.com/creditor/subscription/:agreement/:ref \
  -H 'authorization: **authorization**' \
  -d 'message=mymessage'\
  -d 'plan=planName'\
  -d 'amount=10.22'

HTTP Request

POST https://api.twikey.com/creditor/subscription/:agreement/:ref

:agreement: the mandate reference (e.g.: MNDT123)
:ref : the reference of the subscription

Responsecopy
{
    "id": 10,
    "state": "active",
    "amount": 10.22,
    "message": "mymessage",
    "ref": "reference123",
    "plan": 0,
    "runs": 0,
    "stopAfter": 5,
    "start": "2022-11-29",
    "next": "2022-12-01",
    "recurrence": "1m",
    "mndtId": "TEST03"
}

Query Parameters

NameDescriptionRequiredType
Starts new
Max .
mndtIdMandate ReferenceYesstring
*
when different from the current mandate id
35
messageMessage to the subscriberYesstring140
amountAmount of the transactionYesnumber
startStart date of the subscription (yyyy-mm-dd). This is also the first execution date. Only a future date is acceptedYesdate
*
planName of the base plan **[1]*Nostring
recurrence1w / 1m / 2m / 3m / 4m/ 6m /12m (default 1m)Nostring
*
stopAfterNumber of times to execute **[2]*Nonumber
*
if the value is lower then the current one

**[*1]**: When passing a plan the values of message, amount and recurrence are ignored if passed during the request.
**[*2]**: Previous executions (runs) are not taken into account for the new subscription. It starts from 0 runs.

HTTP Response

CodeDescription
204The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo contract was found
err_invalid_dateInvalid start date
err_plan_rangeInvalid stopAfter value
err_msg_missingMessage not supplied
err_invalid_amountInvalid amount

Patch a subscription

To update a subscription but not replace it, you can use this endpoint. It allows you to update specific fields or move the subscription to a different mandate.

copy
curl -X PATCH https://api.twikey.com/creditor/subscription/MNDT100/myreference
?mndtId=MNDT200&message=mymessage&amount=25
  -H 'authorization: **authorization**' 

HTTP Request

PATCH https://api.twikey.com/creditor/subscription/:agreement/:ref{queryparameters}

:agreement: the mandate reference (e.g.: MNDT123)
:ref : the reference of the subscription

Responsecopy
{
    "id": 10,
    "state": "active",
    "amount": 25.0,
    "message": "mymessage",
    "ref": "myreference",
    "plan": 0,
    "runs": 0,
    "stopAfter": 5,
    "start": "2024-11-29",
    "next": "2024-12-01",
    "recurrence": "1m",
    "mndtId": "MNDT200"
}

Query Parameters

NameDescriptionRequiredTypeMax.
mndtIdMove the subscription to a different mandateNostring35
messageMessage to the subscriberNostring140
amountAmount of the transactionNonumber

HTTP Response

CodeDescription
200The server has fulfilled the request
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo contract was found
err_invalid_amountInvalid amount

Cancel a subscription

A subscription can be cancelled by using it's ref for a specific agreement.

copy
curl -X DELETE https://api.twikey.com/creditor/subscription/:agreement/:ref \
  -H 'authorization: **authorization**' 

HTTP Request

DELETE https://api.twikey.com/creditor/subscription/:agreement/:ref

HTTP Response

CodeDescription
204The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_not_foundNo subscription found (agreement, ref or combination incorrect)

Retrieve a single subscription

A single subscription can be fetched for a specific agreement, but note that this call is rate limited.

copy
curl https://api.twikey.com/creditor/subscription/:agreement/:ref \
  -H 'authorization: **authorization**' 

HTTP Request

GET https://api.twikey.com/creditor/subscription/:agreement/:ref

Responsecopy
{
    "id": 10,
    "state": "active",
    "amount": 12.0,
    "message": "Monthly subscription",
    "ref": "MyRef",
    "plan": 0,
    "runs": 0,
    "stopAfter": 5,
    "start": "2022-11-29",
    "last": null,
    "next": "2022-12-01",
    "recurrence": "1m",
    "mndtId": "MNDT123"
}

Path Parameters

ParameterDescriptionRequired
agreement referenceThe reference of your agreement (eg. MNDT123)Yes
subscription referenceThe unique reference of a subscription for that agreementYes

HTTP Response

CodeDescription
200The server has fulfilled the request
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_not_foundNo subscription found (agreement, ref or combination incorrect)

Query all subscription

All subscription can be retrieved based on some criteria.

This request is rate limited.

copy
curl -X POST https://api.twikey.com/creditor/subscription/query?mndtId=mdntId123&customerNumber=123&state=active \
  -H 'authorization: **authorization**'
Responsecopy
{
  "Subscriptions": [
    {
      "id": 10,
      "state": "active",
      "amount": 12.0,
      "message": "Message for customer",
      "ref": "MyRef",
      "plan": 0,
      "runs": 0,
      "stopAfter": 5,
      "start": "2022-10-29",
      "last": "2022-11-29",
      "next": "2022-12-01",
      "recurrence": "1m",
      "mndtId": "PLOPSAABO3"
    },
    ...
  ]
}

HTTP Request

GET https://api.twikey.com/creditor/subscription/query

Query Parameters

NameDescriptionRequiredType
mndtIdMandate ReferenceNostring
customerNumberSubscriptions by customerNumberNostring
stateState of the subscription (active, suspended, cancelled, closed)Nostring
pagePage of the results (if more than 1 is available)Nonumber

HTTP Response

CodeDescription
200The server has fulfilled the request
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

No specific error codes. When passing incorrect values the response will return an empty subscriptions array.

Action a subscription

A subscription can be suspended/resumed by using it's ref for a specific agreement.

copy
curl -X POST https://api.twikey.com/creditor/subscription/:agreement/:ref/:action \
  -H 'authorization: **authorization**' 

HTTP Request

POST https://api.twikey.com/creditor/subscription/:agreement/:ref/:action

Where action is either 'suspend' or 'resume'

HTTP Response

CodeDescription
204The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_not_foundNo subscription found (agreement, ref or combination incorrect)
err_invalid_paramsInvalid parameter

Transactions

New transaction

Add transaction to an existing mandate. This transaction will be sent to the bank when the collection is sent to the bank either automatically or manually.

HTTP Request

POST /creditor/transaction

copy
curl -X POST https://api.twikey.com/creditor/transaction \
  -H 'authorization: **authorization**'\
  -d 'mndtId=mndtId123' \
  -d 'message=Monthly payment' \
  -d 'amount=10'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,"mndtId=mndtId123"
    ."&message=Monthly payment"
    ."&amount=10");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
const transaction = await client.transaction.create({
    mndtId: "CORERECURRENTNL16318",
    message: "Test message",
    amount: 500,
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void addTransaction(){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("mndtId", "mndtId123")
            .add("message", "Monthly payment")
            .add("amount", "10")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/transaction")
            .post(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; //collected through logIn

    public void addTransaction(){
        RestClient client = new RestClient(host + "/creditor/transaction");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "mndtId=mndtId123" +
            "message=Monthly payment" +
            "amount=10"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "Entries": [
    {
      "id": 381563,
      "contractId": 325638,
      "mndtId": "MNDT123",
      "contract": "Algemene voorwaarden",
      "amount": 10.0,
      "msg": "Monthly payment",
      "place": null,
      "ref": null,
      "date": "2017-09-16T14:32:05Z"
    }
  ]
}

Request Headers

NameDescriptiontypemax. length
Idempotency-KeyUnique key usable only once per request every 24hrsstring64

Request Parameters

NameDescriptionRequiredTypeMax.
mndtIdMandate ReferenceYesstring35
dateDate of the transaction or now when emptyNodate
reqcolldtRequested collection date of the transaction or null to collect as soon as possibleNodate
messageMessage to the customer [*1]Yesstring140
refYour referenceNostring
amountAmount to be billedYesdecimal
placeOptional placeNostring
refase2eThe reference is used as E2E identifier for the first payment
Conform with the Rule Book of the EPC.
Noboolean

[*1]:

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
409Conflict. Idempotency-key already used

Error codes

CodeDescription
err_no_contractNo mandate found
err_invalid_stateThe mandate is not active
err_invalid_dateInvalid Date
err_invalid_sepacharsInvalid characters in message to debtor
err_invalid_amountInvalid Amount given
err_billing_overdrawnMaximum amount reached according to risk rules
err_invalid_paramsSome parameter is invalid, see the extra in the response body for more information.
err_duplicate_refIdempotency-key was already used in the last 24hrs.

Transaction feed

Retrieve list of transactions that had changes since the last call. This endpoint allows to retrieve a list of transactions for which new payment information has been received since the last call. This endpoint doesn't require any parameters. If we receive feedback from the bank, we mark the transaction with status "error" or "paid".

The final flag is important as it indicates whether or not there are still automatic actions going on. True (being final) means that we can't do anything with it anymore. This could be the case for paid transactions as well as for errors where no more automatic actions can be performed. Here an action will be required on your part. False means that we still have actions pending to debit the debtor's account. Note that a paid state paid with final flag on true can be reverted by the bank (refund request by the debtor).

copy
curl https://api.twikey.com/creditor/transaction \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction");
curl_setopt($ch, CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization');
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
const feed = await client.transaction.feed();
for await (const tx of feed) {
    console.log("Updated transaction: ",tx)
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void getTransactions(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transaction")
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void getTransactions(){
        RestClient client = new RestClient(host + "/creditor/transaction");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Entries": [
            {
                "id": 123456789,
                "contractId": 10001,
                "mndtId": "mandateReference",
                "contract": "contractNumber",
                "amount": 99.99,
                "msg": "transaction message",
                "place": "web",
                "ref": null,
                "final": true,
                "state": "PAID",
                "bkdate": "2017-03-21T08:13:21Z",
                "reqcolldt": "2017-03-23T08:13:21Z"
        },
        {
                "id": 987654321,
                "contractId": 2002,
                "mndtId": "mandateReference",
                "contract": "contractNumber",
                "amount": 100,
                "admincharge": 10.0,
                "msg": "transaction message",
                "place": "web",
                "ref": "INVOICE001",
                "final": false,
                "state": "ERROR",
                "bkerror": "MS03",
                "bkmsg": "Geen reden opgegeven",
                "bkdate": "2016-09-21T08:13:21Z",
                "reqcolldt": null
        },
        {
                "id": 56789123,
                "contractId": 3003,
                "mndtId": "mandateReference",
                "contract": "contractNumber",
                "amount": 49.99,
                "msg": "buy item X",
                "place": "web",
                "ref": null,
                "final": true,
                "state": "ERROR",
                "bkerror": "AC04",
                "bkmsg": "Rekening afgesloten",
                "bkdate": "2016-08-29T13:14:40Z",
                "reqcolldt": null
        },
        "..."
    ]
}

Including all parameters:

Responsecopy
{
  "Entries": [
    {
      "id": 218914,
      "contractId": 1320345,
      "mndtId": "MNDT123",
      "contract": "CTR123",
      "amount": 5.63,
      "admincharge": 10.0,
      "msg": "Delivery fee",
      "place": null,
      "ref": "DLVRY EXPRESS 001",
      "date": "2020-12-09T16:07:19Z",
      "final": true,
      "state": "ERROR",
      "bkerror": "AM04",
      "bkmsg": "Insufficient funds",
      "bkdate": "2020-12-09",
      "lastupdate": "2020-12-09T16:07:24Z",
      "bkamount": 0,
      "collection": 9314,
      "reqcolldt": "2020-12-11",
      "link": "https://mycompany.twikey.com/payment/tr_...",
      "actions": [
        {
          "type": "FAIL_SOFT",
          "reason": "AM04",
          "action": "again",
          "at": "2020-12-09T16:07:24Z"
        },
        {
          "type": "FAIL_SOFT",
          "reason": "AM04",
          "action": "backup",
          "at": "2020-12-10T03:30:09Z"
        }
      ]
    }
  ]
}

HTTP Request

GET /creditor/transaction

Headers

Header nameDescription
X-RESUME-AFTERResume the feed after a specific sequence id (requires the parameter include=seq)

include parameter

The include parameter (optional) can be used several times with a different value to include additional information about the transaction in the response.
Example: GET /creditor/transaction?include=collection&include=lastupdate&include=action&include=link

ValueDescription
collectionreturns the batch id
lastupdatereturns the last update in datetime format and 'bkdate' as date.
actionreturns all the dunning steps taken for a transaction
action_paymentreturns only dunning steps taken since the last time the feed was read. If you both include action and action_payment then the action parameter is ignored
linkreturns the url of the dunning payment link (when in dunning)
stagereturns the the current final stage of the transaction
seqreturn a sequence ID in the feed

Dunning types

Dunning actions

Transaction stage values

admincharge

This is returned when a transaction failed and administrative charges were applied.

bkamount

The bkamount is the amount of the transaction was (partially) paid. This value is increased with each partial payment up to a maximum of the total amount.

Possible states

Possible statesDescription
PAIDThe transaction has been executed + final flag info
ERRORThe transaction has not been executed + final flag info

Final flag state

The final flag is only relevant in case of dunning. If a transaction is paid the final state is always true.
Should the transaction change to failed (eg. chargeback) and dunning is configured the final flag changes to false as long as the dunning is running.

Transaction in a state ERROR + final:true require manual action.

Possible statesDescription
TRUENo more actions are pending. Manual action is required if the state is in error
FALSEMore actions are pending to debit the debtor's account

HTTP Response

CodeDescription
200The request has succeeded

Error codes

CodeDescription
err_missing_paramsNo parameter given
err_no_transactionNo link found based on the transaction
err_not_foundNo link found
err_call_in_progressRequest already in progress by another client

Response parameters

NameDescription
dateDate when the transaction was created
bkdateDate when the transaction was booked (received feedback from the bank)
reqcolldtThe requested collection date
bkerrorError code when a transaction failed
bkamountThe amount already paid
collectionThe collection/batch ID in which the transaction was executed

Transaction status

Retrieve the status of transactions at a certain point in time, this may change at any time. We strongly recommend using the transaction feed to keep your system up to date as you'll never miss an update while this is merely a snapshot in time.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/transaction/detail?id=1234 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction/detail?id=1234");
curl_setopt($ch, CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transaction/detail?id=1234',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void getTransactionDetail(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transaction/detail?id=1234")
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void getTransactionDetail(){
        RestClient client = new RestClient(host + "/creditor/transaction/detail?id=1234");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Entries": [
        {
            "id": 1234,
            "amount": 10.0,
            "contract": "Algemene voorwaarden",
            "contractId": 325638,
            "date": "2017-09-16T14:32:05Z",
            "mndtId": "MNDT123",
            "msg": "Monthly payment",
            "place": null,
            "ref": null,
            "state": "OPEN",
            "reqcolldt": "2021-01-11"
        }
    ]
}

Sample response when using multiple parameters:

Responsecopy
{
  "Entries": [
    {
      "id": 558295,
      "contractId": 1826643,
      "mndtId": "MNDT123",
      "contract": "TC",
      "amount": 22.0,
      "admincharge": 5.0,
      "msg": "Monthly payment",
      "place": null,
      "ref": null,
      "date": "2022-03-09T12:56:29Z",
      "final": true,
      "state": "ERROR",
      "bkerror": "AC04",
      "bkmsg": "Account closed",
      "bkdate": "2022-03-09",
      "lastupdate": "2022-03-09T13:30:01Z",
      "bkamount": 0,
      "collection": 16336,
      "link": "https://company.twikey.com/payment/tr_zwBA7H9N5cxbOIw4g5drv2b",
      "reqcolldt": "2022-03-11"
    }
  ]
}

HTTP Request

GET /creditor/transaction/detail

Request Parameters

General parameters

NameDescriptionRequiredType
idid of a transactionNostring
refref of a transactionNostring
mndtIdmandate referenceNostring

state parameter

The state parameter (optional) can be used to return only transactions in a specific state. Most common usage of this is in combination with a mandate id, to retrieve all the transactions in a specific state for that mandate.

Example: GET /creditor/transaction/detail?mndtId=MNDT123&state=error

NamevalueDescriptionType
stateOPENonly return open transactions [*1]string
statePAIDonly return paid transactionsstring
stateERRORonly return transactions in an error statestring
stateUNPAIDonly return transactions in an open/error statestring

include parameter

The include parameter (optional) can be used several times with a different value to include additional information about the transaction in the response.
Example: GET /creditor/transaction/detail?id=12345&include=collection&include=lastupdate&include=link

NameValueDescription
includecollectionreturns the batch id
includelastupdatereturns the last update in datetime format and 'bkdate' as date.
includelinkreturns the url of the dunning payment link (when in dunning)

Final flag

When a transaction is in a PAID or ERROR state, the final flag is returned in the response.
This can be true or false.

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Error codes

CodeDescription
err_invalid_paramsEither id or ref or mndtId is invalid or is empty

Action on transaction

Execute an action on a transaction, this will allow you to change the status or start a specific flow for the given transaction.

HTTP Request

POST /creditor/transaction/action

copy
curl -X POST https://api.twikey.com/creditor/transaction/action \
  -H 'authorization: **authorization**'\
  -d 'id=345' \
  -d 'action=reoffer'
copy
$host = "https://api.twikey.com";
$authorization = null; // collected through login

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction/action");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,"id=345"
    ."&action=reoffer");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transaction/action',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        body :{
            'id' : '345',
            'action': 'reoffer'
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login
    public void addTransaction() {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("id", "345")
            .add("action", "reoffer")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/transaction/action")
            .post(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login

    public void addTransaction() {
        RestClient client = new RestClient(host + "/creditor/transaction/action");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "id=345" +
            'action=reoffer"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

Request Parameters

NameDescriptionRequiredTypeOptions
idTransaction idYesstring
actionAction to execute for the given transactionYesstringpaid, reoffer, backup*, unsettle, archive

*backup: alternative payment method is sent to the customer by email. This can include a payment link or bank transfer details depending on your configuration.

HTTP Response

CodeDescription
204The request has succeeded
201Deletion of the transaction has succeeded
400User error if parameter is given but not valid (available in api error header and response)

Error codes

CodeDescription
err_no_transactionNo transaction found
err_no_contractNo mandate found (or not active)
err_invalid_paramsone of the parameters provided contains invalid data

Update a transaction

Update parameters for an existing transaction.

HTTP Request

PUT /creditor/transaction

copy
curl -X PUT https://api.twikey.com/creditor/transaction \
  -H 'authorization: **authorization**'\
  -d 'id=345' \
  -d 'message=Monthly payment' \
  -d 'amount=10'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS,"id=345"
    ."&message=Monthly payment"
    ."&amount=10");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transaction',
        method: 'PUT',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        body :{
            'id' : '345',
            'message': 'Monthly payment',
            'amount': '10'
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void updateTransaction(){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("id", "345")
            .add("message", "Monthly payment")
            .add("amount", "10")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/transaction")
            .put(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login

    public void updateTransaction(){
        RestClient client = new RestClient(host + "/creditor/transaction");
        RestRequest request = new RestRequest(Method.PUT);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "id=345" +
            "message=Monthly payment" +
            'amount=10"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

Request Parameters

NameDescriptionRequiredType
idTransaction idYesstring
reqcolldtRequested date of the billable eventNostring
messageMessage to the customerNostring
refYour referenceNostring
amountAmount to be billedNostring
placeOptional placeNostring

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in api error header and response)

Error codes

CodeDescription
err_no_transactionNo transaction found
err_no_contractNo mandate found (or not active)
err_invalid_paramsone of the parameters provided contains invalid data
err_invalid_dateInvalid Date
err_invalid_amountInvalid Amount given

Refund a transaction

General transactions

If the beneficiary account does not already exist, the account is added to the customer. The IBAN of the refund account is the one registered on the mandate linked to the transaction. After a refund request, the refund batch need to be prepared to be proccessed.

Credit Card transactions

Currently, supported payment providers for refunds are: CCV, Multisafepay, Mollie In the response the id is 1-on-1 with the refund id from your provider.

copy
curl -X POST https://api.twikey.com/creditor/transaction/refund \
  -H 'authorization: **authorization**'\
  -d 'id=345' \
  -d 'iban=BE12356798' \
  -d 'bic=BBRUBEBB' \
  -d 'message=refund payment' \
  -d 'amount=10'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction/refund");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS,"id=345"
    ."&message=Refund payment"
    ."&iban=BE12356798"
    ."&bic=BBRUBEBB"
    ."&amount=10");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transaction/refund',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        body: {
            'id': '345',
            'message': 'Refund payment',
            'amount': '10',
            'iban': 'BE12356798',
            'bic': 'BBRUBEBB'
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ", result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn

    public void refundTransaction() {
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("id", "345")
            .add("message", "Refund payment")
            .add("amount", "10")
            .add("iban", "BE12356798")
            .add("bic", "BBRUBEBB")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/transaction/refund")
            .post(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    }

    ;
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login

    public void refundTransaction(){
        RestClient client = new RestClient(host + "/creditor/transaction/refund");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "id=345" +
            "&message=Refund payment" +
            "&amount=10"
            "&iban=BE12356798"
            "&bic=BBRUBEBB"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

POST /creditor/transaction/refund

Request Headers

NameDescriptiontypemax. length
Idempotency-KeyUnique key usable only once per request every 24hrsstring64

Request Parameters

NameDescriptionRequiredType
idThe transaction idYesString
messageMessage for the refundYesString
amountAmount to be refundedYesNumber
refAdd a reference for the refundNoString
placePlace of refundNoString
ibanIban of the Beneficiary account (optional otherwise iban from the mandate)NoString
bicBic of the Beneficiary accountNoString
Responsecopy
{
    "Entries": [
        {
            "id": "9FD3492820210119162251192138",
            "iban": "BE123456789",
            "bic": "GKCCBEBB",
            "amount": 10.0,
            "msg": "Refund payment",
            "place": "Ghent",
            "ref": "refund reference 123",
            "date": "2021-01-19"
        }
    ]
}

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_invalid_paramsMissing request parameters or invalid values
err_msg_missingmessage parameter missing

Remove a transaction

Remove a transaction that wasn't sent to the bank yet based on the id and/or reference.

copy
curl -X DELETE https://api.twikey.com/creditor/transaction \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transaction");
curl_setopt($ch, CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transaction',
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ", result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn

    public void removeTransaction() {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transaction")
            .delete(null)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    }

    ;
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void removeTransaction(){
        RestClient client = new RestClient(host + "/creditor/transaction");
        RestRequest request = new RestRequest(Method.DELETE);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

DELETE /creditor/transaction

Request Parameters

At least one parameter must be passed in the request.

NameDescriptionRequiredType
ida transactionId as returned in the postNostring
refTransaction reference (ref) as provided in the post to be removedNostring

HTTP Response

CodeDescription
204The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractContract not found
err_not_foundTransaction not found
err_transaction_invalid_actionAction invalid for the current state of the transaction
err_duplicate_txMultiple transactions with the same reference exist

Query transactions

Sequentially retrieve all created transactions starting from any given transaction ID. This endpoint is meant only for exporting transactions in bulk and registering transactions created by subscriptions.
We strongly recommend using the transaction feed to keep your system up to date as you'll never miss an update while this is merely a snapshot in time.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/transaction/query?fromId=1000000 \
  -H 'authorization: **authorization**' 
Responsecopy
{
    "Entries": [
        {
            "id": 4692115,
            "contractId": 3408156,
            "ct": 3764,
            "subscriptionId": 230863,
            "mndtId": "EXAMPLE-AI3I",
            "amount": 623.0,
            "msg": "Bike",
            "place": null,
            "ref": "F7B2F7F3910D4B139AE2A3D7C20677E4",
            "date": "2024-08-27T08:26:19Z",
            "final": true,
            "state": "PAID",
            "bkdate": "2024-08-23",
            "lastupdate": "2024-08-23T08:40:01Z",
            "collection": 51014
        },
        {
            "id": 4692116,
            "contractId": 3408152,
            "ct": 3764,
            "subscriptionId": 230858,
            "mndtId": "EXAMPLE-AGPWU",
            "amount": 849.0,
            "msg": "Gloves",
            "place": null,
            "ref": "079D43896FA44CEC809222C54402B66C",
            "date": "2024-08-27T08:26:19Z",
            "final": true,
            "state": "PAID",
            "bkdate": "2024-08-23",
            "lastupdate": "2024-08-23T08:40:01Z",
            "collection": 51014
        },
        {
            "id": 4692118,
            "contractId": 3408186,
            "ct": 3764,
            "subscriptionId": 230866,
            "mndtId": "EXAMPLE-AGPVZ",
            "amount": 872.0,
            "msg": "Gloves",
            "place": null,
            "ref": "ACBDD129FBE745838303188F9505DB8E",
            "date": "2024-08-27T08:26:19Z",
            "final": true,
            "state": "PAID",
            "bkdate": "2024-08-23",
            "lastupdate": "2024-08-23T08:40:02Z",
            "collection": 51014
        }
    ],
    "_links": {
        "self": "/creditor/transaction/query?fromId=4692115",
        "next": "/creditor/transaction/query?fromId=4692119"
    }
}

HTTP Request

GET /creditor/transaction/query?fromId=1000000

Query Parameters

NameDescriptionRequiredType
fromIdThe Id of the transaction from where to start your queryYesNumber
mndtIdMandate ReferenceNostring

HTTP Response

CodeDescription
200The request has succeeded
400Bad request
429Too many requests

Error Codes

CodeDescription
err_not_foundThe ID provided was not found in the environment. The ID provided is included under extra.
err_missing_paramsSome parameter is missing, see the extra in the response body for more information.

Collections

Execute Collection

Prepare a batch of transactions for collection and sent to collecting agent (Bank integration) defined on the specified template.

HTTP Request

POST /creditor/collect

copy
curl -X POST https://api.twikey.com/creditor/collect \
  -d ct=123 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$ct = "**ct_id**";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/collect");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "ct=$ct"
    ."clltndt=2017-09-15"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    ct = "**ct_id**",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/collect',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: {
            "ct":"$ct",
            "clltndt":"2017-09-15"
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn
    private String ct = "**ct_id**";

    public void executeCollect(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
            .add("ct", ct)
            .add("clltndt","2017-09-15")
            .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/collect")
          .post(formBody)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void executeCollect(){
        RestClient client = new RestClient(host + "/creditor/collect");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter("application/x-www-form-urlencoded",
            "ct=" + ct +
            "clltndt="2017-09-15"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

Query parameters

NameDescriptionRequiredType
ctContract template for which to do the collectionYesnumber
colltndtCollection date (default=earliest batch) **[1]*Nostring
prenotifyOptional parameter, it's existence will trigger the prenotification towards the debtor (true/false)Noboolean
untilOnly include transactions in the batch created until a specific date - epoch in millisecondsNonumber

**[*1]**: When you set the collection date to a date in the future, all the batches from the current day until the colltndt are directly sent to the bank in one batch.

Responsecopy
{
    "frstMsgId": null,
    "rcurMsgId": "BE81ZZZ08502098492203301148072234"
}

Response Parameters

A batch was created and sent when at least the rcurMsgId or frstMsgId returns a value. When both values are null nothing was sent.

NameDescription
frstMsgIdFirst-type message id (deprecated)
rcurMsgIdRecurring-type message id

HTTP Response

CodeDescription
200Request succeeded. The response returns the id referencing both first and recurring sdd send to bank
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_such_ctNo template specified
err_invalid_ctInvalid template specified
err_provide_accountNo recurring gateway configured on the profile (ct)
err_no_access_subscriptionNo connection to the bank in your subscription
err_invalid_dateInvalid date
err_invalid_paramsInvalid parameters passed in the request

Status Collection

This endpoint allows to retrieve an SDD batch. In case we receive an error from the bank, we mark the transaction with status "error" or "paid", but on top of it we add a final flag which will either be true or false.

The final flag is important as it indicates whether or not there are still automatic actions going on. True (being final) means that we can't do anything with it anymore. This could be the case for paid transactions as well as for errors where no more automatic actions can be performed. Here an action will be required on your part. False means that we still have actions pending to debit the debtor's account. Note that paid can be reverted by the bank.

This request is rate limited.

HTTP Request

GET /creditor/collect

copy
curl https://api.twikey.com/creditor/collect \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/collect");
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");
curl_setOpt($ch, CUSTOMREQUEST, 'GET');
$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/collect',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorization = null; //collected through logIn

    public void statusCollection(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .url(host + "/creditor/collect")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorization)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void statusCollection(){
        RestClient client = new RestClient(host + "/creditor/collect");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Sdds":[
        {
            "id":1437939405111,
            "pmtinfid": "mypmtid",
            "tx":4,    // Number of transactions
            "amount":120.0,   // Total amount of the transactions
            "Entries": [
                {
                    "e2eid": "mye2e",
                    "amount": 30.0,
                    "contractId": 7488, // internal reference to the contract
                    "mandateRef": "TWIKEYCORE22",  // mandate reference
                    "msg": "testing",
                    "final": false,  // Does Twikey have any outstanding actions ?
                    "state": "open"  // No payment information received as of yet
                },
                {
                    "e2eid": "mye2e",
                    "amount": 30.0,
                    "contractId": 7488,
                    "mandateRef": "TWIKEYCORE22",
                    "msg": "testing",
                    "final": true,
                    "state": "paid",  // Transaction was paid
                    "bkamount": 30.0,  // Booked amount as stated in account information
                    "bkdate": "2015-07-27",  // Booking date as stated in account information
                    "bkerror": null   // No errors
                },
                {
                    "e2eid": "mye2e",
                    "amount": 30.0,
                    "contractId": 6358,
                    "mandateRef": "TWIKEYCORE21",
                    "msg": "test",
                    "final": false, // This is FYI, since we haven't exhausted yet all possibilities (will continue dunning)
                    "state": "error", // Transaction was paid but customer requested a refund
                    "bkamount": 30.0,
                    "bkdate": "2015-07-27",
                    "bkerror": "MD06" // Actual error code (refund request)
                },
                {
                    "e2eid": "mye2e",
                    "amount": 30.0,
                    "contractId": 6358,
                    "mandateRef": "TWIKEYCORE21",
                    "msg": "test",
                    "final": true,  // Could not collect, please contact your customer
                    "state": "error",
                    "bkamount": 30.0,
                    "bkdate": "2015-07-27",
                    "bkerror": "AG01"
                }
            ]
        }
    ]
}

Query parameters

NameDescriptionRequiredType
idSpecific SDD referenceNonumber
pmtinfidSpecific Payment identifierNostring

HTTP Response

CodeDescription
200The request has succeeded
429Too many requests

Error Codes

ErrorDescription
err_call_in_progressRequest already in progress by another client

Import Collection

Import a direct debit batch for collection. When the batch is imported and valid, it is directly sent to the bank for processing. Accepted format: Pain 008.001.02 (ISO 20022)

A profile id needs to be passed using the ctquery parameter.

HTTP Request

POST /creditor/collect/import?ct=1234 The Direct Debit message can be imported as file or as XML in the body of the request.

copy
curl --location --request POST 'https://api.twikey.com/creditor/collect/import?ct=1234' \
-h 'authorization: **authorization**' \
-h 'Content-Type: application/xml' \
-d '@path/to/batch.xml'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/collect/import?ct=1234',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => "<file contents here>",
  CURLOPT_HTTPHEADER => array(
    'Authorization: $authorization',
    'Content-Type: application/xml'
  ),
));

$response = curl_exec($curl);
$result = json_decode($response);

curl_close($curl);
copy
var data = "<file contents here>",
    authorization = null; //collected through login

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function() {
  if(this.readyState === 4) {
    console.log(this.responseText);
  }
});

xhr.open("POST", "https://api.twikey.com/creditor/collect/import?ct=1234");
xhr.setRequestHeader("Authorization", authorization);
xhr.setRequestHeader("Content-Type", "application/xml");

xhr.send(data);
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorization = null; //collected through logIn

    public void importCollection(){
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/xml");
RequestBody body = RequestBody.create(mediaType, "<file contents here>");
Request request = new Request.Builder()
  .url(host + "/creditor/collect/import?ct=1234")
  .method("POST", body)
  .addHeader("Authorization", authorization)
  .addHeader("Content-Type", "application/xml")
  .build();
Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
                    authorization = null; // collected through logIn

    public void importCollection(){
var client = new RestClient(host + "/creditor/collect/import?ct=1234");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", authorization);
request.AddHeader("Content-Type", "application/xml");
request.AddParameter("application/xml", "<file contents here>", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
  }
}

Query parameters

ParameterDescriptionRequiredType
ctThe profile ID to import the collection onYesNumber

HTTP Response

CodeDescription
204The request has succeeded
400Bad request

Error codes

CodeDescription
invalid_fileInvalid format or file already uploaded*

*Collection with the same value of 'PmtInfId' and 'EndToEndId' was already uploaded.

Query collections

Create a search query to return all collections with a specific status, requested collection date, of a specific profile or a combination of different query parameters.
The response is limited to a set of 50 collections per page. The response returns the collections found based on your search query.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/collect/query?ct=1234&reqcolldt=2024-05-06 \
  -H 'authorization: **authorization**' \
copy
$host = "https://api.twikey.com";
$ct = **ct_id**;$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/collect/query?ct=1234&reqcolldt=2024-05-06");
curl_setOpt($ch, CURLOPT_HTTPHEADER, "authorization: $authorization");

$server_output = curl_exec ($ch);
$result = json_decode($server_output);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/collect/query?ct=1234&reqcolldt=2024-05-06',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log(res);
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void updateFeed(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
          .url(host + "/creditor/collect/query?ct=1234&reqcolldt=2024-05-06")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        authorisation = null; // collected through logIn

    public void updateFeed(){
        RestClient client = new RestClient(host + "/creditor/collect/query?ct=1234&reqcolldt=2024-05-06");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "_links": {
        "self": "/creditor/collect/query?ct=2223&reqcolldt=2024-05-06&page=0"
    },
    "collections": [
        {
            "id": 47737,
            "pmtinfid": "BE81ZZZ0850209849-240503033438-2223",
            "ct": 1234,
            "tx": 25,
            "amount": 578.55,
            "status": "Sent",
            "reqcolldt": "2024-05-06",
            "generated": "2024-05-03",
            "progress": "download"
        },
        {
            "id": 47695,
            "pmtinfid": "BE81ZZZ0850209849-240502080510-2223",
            "ct": 1234,
            "tx": 66,
            "amount": 596.99,
            "status": "Sent",
            "reqcolldt": "2024-05-06",
            "generated": "2024-05-02",
            "progress": "sent"
        }
    ]
}

HTTP Request

GET https://api.twikey.com/creditor/collect/query?parameter=value

Headers

By default a json response is returned, but you can opt to return the body in XML.

HeaderValueDescription
Acceptapplication/xmlreturn the response in XML format instead of json

Query Parameters

All parameters are optional. A combination of these parameters is possible.

NameDescriptionRequiredType
statesent, archived or cancelledNostring
generatedCreation date of the collection batch (YYYY-MM-DD)Nodate
reqcolldtRequested collection date of the batch (YYYY-MM-DD)Nodate
ctProfile IDNonumber
pagePagination is returned at the beginning of the response, include 'page' with a page number to go to the next or previous set of resultsNonumber
fromCollections created from (including) a precise date local date (YYYY-MM-DD)Nodate
untilCollections created until (excluding) a precise date local date (YYYY-MM-DD) - default is todayNodate

until and from

Example: until=2024-05-30 and from=2024-05-31 to include everything from 2024-05-30 00:00:00 to 2024-05-30 23:59:59.

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Error codes

CodeDescription
err_invalid_stateInvalid state passed
err_missing_paramsMissing parameters or incorrect parameter value passed.

Invoices

Create invoice

Create a new invoice for a customer. The invoice can be sent to your customer automatically or manually. Depending on the selected template, different payment options are provided to the customer.

It is possible to create invoices with a zero amount. Those will directly be set on 'paid' after import.

HTTP Request

POST /creditor/invoice

copy
curl -X POST https://api.twikey.com/creditor/invoice \
  -H 'authorization: **authorization**' \
  -H "Content-Type: application/json" \
  -d '{
             "number": "Inv20200001",
             "title": "Invoice July",
             "remittance": "596843697521",
             "ct": 1988,
             "amount": 100,
             "date": "2020-01-31",
             "duedate": "2020-02-28",
             "locale": "nl",
             "customer": {
               "customerNumber": "customer123",
               "email": "no-reply@twikey.com",
               "firstname": "Twikey",
               "lastname": "Support",
               "address": "Stationstraat 43",
               "city": "Gent",
               "zip": "9000",
               "country": "BE",
               "l": "fr",
               "mobile": "32498665995"
             },
             "pdf": "JVBERi0xLj....RU9GCg=="
     }'

**Or by referencing an existing document or customerNumber**

curl -X POST https://api.twikey.com/creditor/invoice \
  -H 'authorization: **authorization**' \
  -H "Content-Type: application/json" \
  -d '{
             "number": "Inv20200001",
             "title": "Invoice July",
             "remittance": "596843697521",
             "ct": 1988,
             "amount": 100,
             "date": "2020-01-31",
             "duedate": "2020-02-28",
             "customerByRef": "customer123"  // or "customerByDocument": "MandateReference123"
             "pdf": "JVBERi0xLj....RU9GCg=="
     }'
copy
$client = new http\Client;
$request = new http\Client\Request;
$request->setRequestUrl('http://api.twikey/creditor/invoice');
$request->setRequestMethod('POST');
$body = new http\Message\Body;
$body->append('{
             "number": "Invss123",
             "id": "fec44175-b4fe-414c-92aa-9d0a7dd0dbf2",
             "title": "Invoice April",
             "remittance": "123456789123",
             "ct": 60,
             "amount": 100,
             "date": "2020-03-20",
             "duedate": "2020-04-28",
             "locale": "nl",
             "customer": {
               "customerNumber": "customer123",
               "email": "no-reply@twikey.com",
               "firstname": "Twikey",
               "lastname": "Support",
               "address": "Stationstraat 43",
               "city": "Gent",
               "zip": "9000",
               "country": "BE",
               "lang": "fr",
               "mobile": "32498665995"
                }
 }');
$request->setBody($body);
$request->setOptions(array());
$request->setHeaders(array(
  'Authorization' => '...',
  'Content-Type' => 'application/json'
));
$client->enqueue($request)->send();
$response = $client->getResponse();
echo $response->getBody();
copy
const invoice = await client.invoice.create({
    "number": "Invoice123",
    "id": "fec44175-b4fe-414c-92aa-9d0a7dd0dbf2",
    "title": "Invoice April",
    "remittance": "123456789123",
    "ct": 60,
    "amount": 100,
    "date": "2020-03-20",
    "duedate": "2020-04-28",
    "locale": "nl",
    "customer": {
        "customerNumber": "customer123",
        "email": "no-reply@twikey.com",
        "firstname": "Twikey",
        "lastname": "Support",
        "address": "Stationstraat 43",
        "city": "Gent",
        "zip": "9000",
        "country": "BE",
        "lang": "fr",
        "mobile": "32498665995"
    }
});
copy
OkHttpClient client = new OkHttpClient().newBuilder()
        .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n" +
        "  \"number\": \"Invss123\",\n" +
        "  \"id\" : \"fec44175-b4fe-414c-92aa-9d0a7dd0dbf2\",\n" +
        "  \"title\": \"Invoice April\",\n" +
        "  \"remittance\": \"123456789123\",\n" +
        "  \"ct\": 60,\n" +
        "  \"amount\": 100,\n" +
        "  \"date\": \"2020-03-20\",\n" +
        "  \"duedate\": \"2020-04-28\",\n" +
        "  \"customer\": {\n" +
        "    \"customerNumber\": \"customer123\",\n" +
        "    \"email\": \"no-reply@twikey.com\",\n" +
        "    \"firstname\": \"Twikey\",\n" +
        "    \"lastname\": \"Support\",\n" +
        "    \"address\": \"Stationstraat 43\",\n" +
        "    \"city\": \"Gent\",\n" +
        "    \"zip\": \"9000\",\n" +
        "    \"country\": \"BE\",\n" +
        "    \"lang\": \"nl\",\n" +
        "    \"mobile\": \"32498665995\"\n" +
        "  }\n" +
        "}");
Request request = new Request.Builder()
        .url("https://api.twikey.com/creditor/invoice")
        .method("POST", body)
        .addHeader("Authorization", "701fbbfd-2681-47c3-8077-59345df335f2")
        .addHeader("Content-Type", "application/json")
        .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("http://api.twikey/creditor/invoice");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "701fbbfd-2681-47c3-8077-59345df335f2");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n" +
        "  \"number\": \"Invss123\",\n" +
        "  \"id\" : \"fec44175-b4fe-414c-92aa-9d0a7dd0dbf2\",\n" +
        "  \"title\": \"Invoice April\",\n" +
        "  \"remittance\": \"123456789123\",\n" +
        "  \"ct\": 60,\n" +
        "  \"amount\": 100,\n" +
        "  \"date\": \"2020-03-20\",\n" +
        "  \"duedate\": \"2020-04-28\",\n" +
        "  \"locale\": \"nl\",\n" +
        "  \"customer\": {\n" +
        "    \"customerNumber\": \"customer123\",\n" +
        "    \"email\": \"no-reply@twikey.com\",\n" +
        "    \"firstname\": \"Twikey\",\n" +
        "    \"lastname\": \"Support\",\n" +
        "    \"address\": \"Stationstraat 43\",\n" +
        "    \"city\": \"Gent\",\n" +
        "    \"zip\": \"9000\",\n" +
        "    \"country\": \"BE\",\n" +
        "    \"lang\": \"fr\",\n" +
        "    \"mobile\": \"32498665995\"\n" +
        "  }\n" +
        "}",  ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Responsecopy
{
      "id": "fec44175-b4fe-414c-92aa-9d0a7dd0dbf2",
      "number": "Inv20200001",
      "title": "Invoice July",
      "ct": 1988,
      "amount": 100.00,
      "date": "2020-01-31",
      "duedate": "2020-02-28",
      "status": "BOOKED",
      "url": "https://app.twikey.com/<company_id>/fec44175-b4fe-414c-92aa-9d0a7dd0dbf2"
}

Headers:

HeaderValueDescription
X-PURPOSEqr (by default)Returns a url that can be used in different bank apps.
X-PURPOSEredirectReturns a branded-url for generic use cases (faster)
X-PURPOSEmerchantReturns a url that can be used over the counter. The payment page includes a large QR code on screen
X-PARTNERstringAlter 'Api' in 'Invoice was registered via Api' (only visible in the interface)
X-FORCE-TRANSACTIONbooleanCreate a transaction for the invoice when the mandate is suspended or cancelled (requires a reference to a mandate in the body of the request

Request Parameters

Invoice Object

language:

The language in the invoice object can be passed using the parameter 'l' or 'locale'. When language is not passed:

Specific locales:

The language passed in the customer object has no impact on the language of the invoice.

NameDescriptionRequiredTypeMax.
idUUID of the invoice (optional)Nostring36
numberInvoice number [1]Yesstring50
titleMessage to the debtor [2], if empty one is generated using the invoice number.[2a]Nostring140
remittancePayment message, if empty then title will be used [3]Nostring140
refYour internal referenceNostring
ctTemplate to be used [4]Nostring4
amountAmount to be billedYesnumber
dateInvoice dateYesdate
duedateDue dateYesdate
localeLanguage of the invoice (en, nl, nl_nl, fr, fr_fr, de ,it ,pt, es)Nostring2 - 5
customerCustomerYesobject
customerByDocumentCustomer by document referenceNostring
manualtrue: Do not auto collect the invoice via a recurring mechanism on creation. The invoice can still be linked to a mandate afterwards.Noboolean
pdfBase64 encoded documentNostring
redirectUrlOptional redirect after pay url. A specific app protocol is required (eg. http(s)://). Verify that your PSP supports the specific protocol.Nourl
emailUse a different invoicing email address for this invoice [5].Nostring70
relatedInvoiceNumberLink a credit note to an existing invoice by number [6].Nostring

Customer Object

NameDescriptionRequiredTypeMax.
customerNumberThe customer number to link this invoice too (strongly advised)Yes*string/number50
emailDebtor's emailYes*string70
firstnameDebtor's first name - if not known we will put unknownNostring50
lastnameDebtor's last name - if not known we will put unknownNostring50
companyNameName of the companyNostring140
cocEnterprise number of the companyNostring50
lang / lDebtor's languageNostring2
addressDebtor's address (street + number)Nostring70
cityDebtor's cityNostring50
zipDebtor's zipcodeNostring50
countryDebtor's countryNostring2
mobileDebtor's mobile number placeNostring50

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Possible states

Possible statesDescription
BOOKEDThe invoice created but not processed by the bank
PENDINGThe payment is in progress
EXPIREDThe invoice has expired

Error codes

CodeDescription
err_no_contractNo mandate found
err_debtor_not_foundDebtor was not found
err_invalid_stateThe mandate is not active
err_invalid_dateInvalid Date
err_invalid_sepacharsInvalid characters in message to debtor
err_invalid_amountInvalid Amount given
err_billing_overdrawnMaximum amount reached according to risk rules
err_debtor_id_missingA customer number or email address is missing in the customer object
err_invalid_emailEmail invalid
err_invalid_name_lengthfirst and/or last name is too long
register_lastname_missing'lastname' is empty
register_firstname_missing'firstname' is empty

Credit notes

copy
curl -X POST https://api.twikey.com/creditor/invoice \
  -H 'authorization: **authorization**' \
  -H "Content-Type: application/json" \
  -d '{
             "number": "CN20200001",
             "title": "Credit note July",
             "remittance": "596843697521",
             "relatedInvoiceNumber": "IV20200001",
             "ct": 1988,
             "amount": -100,
             "date": "2024-07-31",
             "duedate": "2024-08-28",
             "locale": "nl",
             "customerByRef": "123456"
     }'

The request to create a credit note is the same as for invoices.
You can use all the parameters available and use a negative amount instead to create a credit note.

Related invoice

In the body of the request you can add the parameter relatedInvoiceNumber to link an existing invoice to that credit note. Multiple credit notes can be linked to the same invoice.

Booked invoices

When the related invoice is in a booked (or expired) state, the amount of the credit note is deducted from the invoice in case of a partial amount. The remaining amount of the invoice can still be paid by the debtor. The credit note is directly marked as paid.

Multiple credit notes can be linked to one invoice, when the full amount of the invoice is reached, the invoice is marked as paid.

Paid invoices

When the related invoice is already paid, the amount of the credit note is not deducted from the invoice and the credit note remains in a booked state.

Update invoice

Update invoice details. Use the unique invoice id in the request url.

HTTP Request

PUT /creditor/invoice/{{invoice_uid}}

copy
curl -X PUT https://api.twikey.com/creditor/invoice/{{id}}

  -H 'authorization: **authorization**' \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Invoice Updated",
  "date": "{{invoiceDate}}",
  "duedate": "{{dueDate}}",
  "ref": "reference",
  "pdf": "JVBERi...RU9GCg==",
  "status": "paid",
  "extra": {
    "myAttribute": "new value"
  }
}'
Responsecopy
{
    "id": "eff74b4e-5f9b-43de-ac1c-77d52736c66f",
    "number": "49954184",
    "title": "Invoice Updated",
    "remittance": "+++211/6853/5187+++",
    "ref": "reference",
    "state": "PAID",
    "amount": 6.0,
    "date": "2022-03-30",
    "duedate": "2022-04-13",
    "ct": 2223,
    "url": "https://app.twikey.com/11475/eff74b4e-5f9b-43de-ac1c-77d52736c66f"
}

Request parameters

NameDescriptionRequiredType
titleTitle of the invoiceNoString
dateInvoice dateYesString
duedateInvoice due dateYesString
refInvoice referenceNoString
pdfBase64 encoded documentNoString
statusMark invoice as "booked", "archived", "paid"NoString
extraInclude custom attribute(s) in the 'extra' object to add or update the value of your custom attributeNoObject/String

HTTP Response

CodeDescription
200The request has succeeded

Error codes

CodeDescription
err_not_foundInvoice id not found
err_invalid_datedate or due date is invalid
err_invalid_stateInvoice state does not allow updates
err_invalid_paramsInvalid request

Delete invoice

This request allows you to delete invoices using the uuid when the state is not paid or in progress.
If the invoice has a PDF attached, it is also removed.

HTTP Request

DEL /creditor/invoice/{{invoice_uuid}}

copy
curl -X DEL https://api.twikey.com/creditor/invoice/{{uuid}}

  -H 'authorization: **authorization**' \

Request parameters

Only the invoice unique identifier is required in the request url.

HTTP Response

CodeDescription
204No content - invoice is deleted
400Bad request

Error codes

CodeDescription
err_not_foundInvoice not found
err_not_authorizedThe state of the invoice does not allow removal (PAID or in PROGRESS)

Invoice feed

Retrieve the list of updates on invoices that had changes since the last call.

copy
curl https://api.twikey.com/creditor/invoice \
  -H 'authorization: **authorization**'
Responsecopy
{
  "Invoices": [
    {
      "id": "b3340469-2bc6-4d20-b717-5d3015979038",
      "number": "INVOICE_8317363890",
      "title": "B8 Sales, Inc.",
      "remittance": "B7-64-3C-22-37-4C",
      "ref": "my reference",
      "state": "PAID",
      "amount": 1.8,
      "date": "2020-10-12",
      "duedate": "2020-10-13",
      "ct": 2223,
      "url": "https://app.twikey.com/<company_id>/b3340469-2bc6-4d20-b717-5d3015979038"
    },
    {
      "Example including lastpayment and meta data": "",
      "invoice data": "...",
      "lastpayment": [
        {
          "action": "payment_fail",
          "date": "2022-02-23",
          "e2e": "CF4013-20220223103125095074696-0",
          "iban": "BE86050472131450",
          "id": 309712,
          "method": "sdd",
          "pmtinf": "BE81ZZZ0850209849-220223103125-2223",
          "rc": "MS03"
        },
        {
          "action": "payment_fail",
          "date": "2022-02-25",
          "e2e": "CF4013-20220223103125095074696-0",
          "iban": "BE86050472131450",
          "id": 309712,
          "method": "sdd",
          "pmtinf": "BE81ZZZ0850209849-220223103125-2223",
          "rc": "MS03"
        },
        {
          "action": "payment",
          "date": "2022-02-27T10:31:32Z",
          "link": 429077,
          "method": "paylink"
        }
      ],
      "meta": {
        "lastError": "MS03"
      }
    }
  ]
}
copy
$host = "https://api.twikey.com";
$authorization = null; //collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/invoice");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/invoice',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void getTransactions(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/invoice")
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void getTransactions(){
        RestClient client = new RestClient(host + "/creditor/invoice");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

GET /creditor/invoice

Request Parameters

include Parameter

The include parameter (optional) can be used several times with a different value to include additional information about the invoices in the response. Example: GET /creditor/invoice?include=lastpayment&include=meta&include=customer

lastpayment

Returns a collection of information about the last payment(s) received for an invoice. The response object can have multiple actions, one for each payment (attempt) done for the invoice.

ValueDescriptionRequiredType
metaInclude meta data in the response (more information below)Nostring
customerInclude customer detailsNostring
lastpaymentList details about the method and state of executed paymentsNostring
ParameterDescription
methodthe method used for (attempted) payment: sdd (Direct Debit), paylink (payment link), transfer (wire transfer) or manual (marked as paid by a user)
actionThe action received, a payment or payment_fail
idTransaction identifier when method is sdd
e2eEnd to End identifier when method is sdd
pmtinfPayment information when method is sdd
ibanThe account number from which the amount was debited when method is sdd or transfer
bicBic code of the account number when method is transfer
rcReturn code received from the bank when method is sdd (PAID or Error code)
linkPaymentlink identifier when method is paylink
msgThe invoice title when method is 'transfer' or a user-message when method is 'manual'
dateDate of the action yyyy-mm-dd (timestamp included when method is paylink or manual)
doublewhen the invoice was already paid and we received a payment for the same invoice (boolean)

meta

Returns meta-information about the invoice.

ParameterDescription
reminderLevelthe number of reminders sent out for the invoice
partialpayment link id used to pay a partial amount of the invoice. This is only possible when a specific payment link was created to do so.
lastErrorlast error received from the bank in case of an Direct Debit transaction
nameCompany name of the debtor
paymentMethodthe method used to pay the invoice (e.g.: visa, mastercard, bancontact, direct_debit,..)

customer

Returns the full customer-object in the response.

Possible states

Possible statesDescription
BOOKEDThe invoice created but not processed by the bank
PENDINGThe payment is in progress
PAIDThe invoice has been paid
EXPIREDThe invoice has expired
ARCHIVEDThe invoice has been archived

HTTP Response

CodeDescription
200The request has succeeded

Error Codes

CodeDescription
err_call_in_progressRequest already in progress by another client

Invoice details

Retrieve the details of an invoice. This can be done on either the invoice unique identifier or invoice number.

This request is rate limited.

copy
curl https://api.twikey.com/creditor/invoice/{{invoice}} \
  -H 'authorization: **authorization**'
Responsecopy
    {
      "id": "b3340469-2bc6-4d20-b717-5d3015979038",
      "number": "INVOICE_8317363890",
      "title": "B8 Sales, Inc.",
      "remittance": "B7-64-3C-22-37-4C",
      "ref": "my reference",
      "state": "PAID",
      "amount": 1.8,
      "date": "2020-10-12",
      "duedate": "2020-10-13",
      "ct": 2223,
      "url": "https://app.twikey.com/<company_id>/b3340469-2bc6-4d20-b717-5d3015979038"
    }
copy
$host = "https://api.twikey.com";
$authorization = null; //collected through logIn
$invoice = "invoice123";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/invoice/$invoice");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/invoice/invoice123',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null, //collected through logIn
        invoice = "invoice123";
    public void getTransactions(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/invoice/" + invoice)
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null, // gathered through logIn
        invoice = "invoice123";
    public void getTransactions(){
        RestClient client = new RestClient(host + "/creditor/invoice/" + invoice);
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

GET /creditor/invoice/{{invoice}}

Request Parameters

include Parameter

The include parameter (optional) can be used several times with a different value to include additional information about the invoices in the response. Example: GET /creditor/invoice/{{invoiceID_or_Number}}?include=lastpayment&include=meta&include=customer

lastpayment

Returns a collection of information about the last payment(s) received for an invoice. The response object can have multiple actions, one for each payment (attempt) done for the invoice.

ParameterDescription
methodthe method used for (attempted) payment: sdd (Direct Debit), paylink (payment link), transfer (wire transfer) or manual (marked as paid by a user)
actionThe action received, a payment or payment_fail
idTransaction identifier when method is sdd
e2eEnd to End identifier when method is sdd
pmtinfPayment information when method is sdd
ibanThe account number from which the amount was debited when method is sdd or transfer
bicBic code of the account number when method is transfer
rcReturn code received from the bank when method is sdd (PAID or Error code)
linkPaymentlink identifier when method is paylink
msgThe invoice title when method is 'transfer' or a user-message when method is 'manual'
dateDate of the action yyyy-mm-dd (timestamp included when method is paylink or manual)
doublewhen the invoice was already paid and we received a payment for the same invoice (boolean)

meta

Returns meta-information about the invoice.

ParameterDescription
reminderLevelthe number of reminders sent out for the invoice
partialpayment link id used to pay a partial amount of the invoice. This is only possible when a specific payment link was created to do so.
lastErrorlast error received from the bank in case of an Direct Debit transaction
nameCompany name of the debtor

customer

Returns the full customer-object in the response.

Possible states

Possible statesDescription
BOOKEDThe invoice created but not processed by the bank
PENDINGThe payment is in progress
PAIDThe invoice has been paid
EXPIREDThe invoice has expired
ARCHIVEDThe invoice has been archived

HTTP Response

CodeDescription
200The request has succeeded
429Too many requests

Error Codes

CodeDescription
err_call_in_progressRequest already in progress by another client

Action on Invoice

Action request enables to send the invitation or reminder to a customer, reoffer invoices and do a refund for credit notes. Invitations and reminders can be done by sms or email. The reminder level (1 - 4) is based accordingly on prior reminders already send to the customer. Type smsreminder has only one reminder level.

HTTP Request

POST /creditor/invoice/{{id}}/action

copy
curl -X POST https://api.twikey.com/creditor/invoice/${invoiceId}/action \
  -H 'authorization: **authorization**' \
  -d 'type=sms'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://api.twikey.com/creditor/invoice/$invoiceId/action",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "type=sms",
  CURLOPT_HTTPHEADER => array(
    "Authorization: $authorization",
    "Content-Type: application/x-www-form-urlencoded"
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "type=sms");
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/invoice/" + invoiceId)
  .method("POST", body)
  .addHeader("Authorization", authorization)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/invoice/{invoiceId}/action");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "{authorization}");
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("type", "sms");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

Request Parameters

NameDescriptionRequiredType
typeThe type of actionyesstring

Type values

NameDescription
emailSend invitation by email
smsSend invitation by sms
reminderSend a reminder by email
smsreminderSend a reminder by sms
letterSend only the invoice letter via postal service (letter)
letterWithInvoiceSend both the letter and invoice via postal service (letter and invoice PDF)
invoiceSend only the invoice via postal service (invoice PDF)
reofferOffer the invoice to the bank for collection via SDD (transactions still needs to be sent to the bank)
peppolSend the invoice over the Peppol network *1

HTTP Response

CodeDescription
204The request has succeeded

Error codes

CodeDescription
err_not_foundInvoice not found
err_invalid_emailDebtor has no email configured
err_invalid_mobileDebtor has no mobile number configured
err_invalid_typeType was not defined or invalid
err_contact_supportMost commonly the requested type is not enabled
err_no_credit_noteThe invoice is not a credit note. only a credit note can be refunded.
err_invalid_stateThe state of the invoice or the state of the related invoice (to a credit note) does not allow the action.
no beneficiary accountA beneficiary account is required to refund the credit note

Upload UBL

Invoices can be uploaded via UBL directly.

HTTP Request

POST /creditor/invoice/ubl Content-Type: application/xml

copy
curl -X POST https://api.twikey.com/creditor/invoice/ubl \
  -H 'authorization: **authorization**' \
  -H "Content-Type: application/xml"
Responsecopy
{
    "id": "30dc0701-0ee0-4f02-b268-1956e649f33d",
    "number": "INVOICE_2103386169",
    "title": "Facture 2103386169",
    "remittance": null,
    "ref": null,
    "state": "BOOKED",
    "amount": 35.8,
    "date": "2022-03-29",
    "duedate": "2022-04-11",
    "ct": 2223,
    "url": "https://app.twikey.com/11475/30dc0701-0ee0-4f02-b268-1956e649d99d"
}

Headers

HeaderDescriptionValue
X-MANUALWhen set on 'true' the invoice is not auto collected via a recurring mechanism on import.boolean
X-INVOICE-IDUse your own UUID for the invoice instead of a Twikey generated iduuid

Mapping

When uploading invoices using UBL files, we map the following fields to their corresponding UBL elements. A reference to the customer is required, without it the file can't be imported. Here you can find the mapping:

customerNumber

customerNumber In this particular order:

  1. cac:AccountingCustomerParty > cbc:SupplierAssignedAccountID
  2. cac:AccountingCustomerParty > cbc:AdditionalAccountID
  3. cac:AccountingCustomerParty > cac:Party > cac:PartyIdentification > cbc:ID
  4. cac:AccountingCustomerParty > cac:Party > cac:PartyIdentification > cbc:CompanyID
  5. cac:AccountingCustomerParty > cbc:CustomerAssignedAccountID

* The mapping can be overruled by one fixed field you want to use for the customer number. Contact our support to enable this.

Other fields

DescriptionMapping
Remittance (v2.1*)cac:PaymentMeans > cbc:InstructionID
languagecac:AccountingCustomerParty > cac:Party > cbc:Language
emailcac:AccountingCustomerParty > cac:Party > cac:Contact > cbc:Email
mobilecac:AccountingCustomerParty > cac:Party > cac:Contact > cbc:Telephone
Company Namecac:AccountingCustomerParty > cac:Party > cac:PartyName > cbc:Name
coc In this particular order:
cac:AccountingCustomerParty > cac:PartyIdentification > cbc:Id
cac:AccountingCustomerParty > cac:PartyIdentification > cbc:CompanyId
streetcac:AccountingCustomerParty > cac:Party > cac:PostalAddress > cbc:StreetName
citycac:AccountingCustomerParty > cac:Party > cac:PostalAddress > cbc:CityName
zipcac:AccountingCustomerParty > cac:Party > cac:PostalAddress > cbc:PostalZone
countrycac:AccountingCustomerParty > cac:Party > cac:PostalAddress > cbc:Country

HTTP Response

CodeDescription
200The request has succeeded
400Bad request (user-error)

Error codes

CodeDescription
invalid_fileFile is invalid or no file was provided

Reconciliation

Generate Files

Reconciliation files can be generated in CAMT053 or CODA. Direct debit files are available 3 days after sending the transactions to the bank. Payment link files are available every day for payments done the prior day(s).

When using Mollie for your settlements the frequency can vary. Please contact Mollie for more information about settlements.

HTTP Request

POST /creditor/files

copy
curl POST 'https://api.twikey.com/creditor/files' \
--h 'Authorization: .....' \
--d 'sdd=false' \
--d 'format=coda' \
--d 'paylink=true'
copy

$ch = curl_init();
$authorization = null; // collected through logIn

curl_setopt_array($ch, array(
  CURLOPT_URL => "https://api.twikey.com/creditor/files",
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "sdd=true&paylink=true&format=coda",
  CURLOPT_HTTPHEADER => array(
    'Authorization: $authorization'
  ),
));

$server_output = curl_exec($ch);
curl_close($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/files',
        method: 'POST',
        headers: {
            'Authorization': authorization
        },
        body: {
            'sdd': 'true',
            'paylink': 'true',
            'format': 'coda'
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "sdd=false&format=coda&paylink=true");
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/files")
  .method("POST", body)
  .addHeader("Content-Type", "application/x-www-form-urlencoded")
  .addHeader("Authorization", "....")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/files");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "....");
request.AddParameter("sdd", "false");
request.AddParameter("format", "coda");
request.AddParameter("paylink", "true");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

Query parameters

NameDescriptionRequiredType
sddSet true to generate files for recurring transactions (direct debit and credit card)Yesboolean
paylinkSet true to generate paylink filesYesboolean
formatthe format of the file (see Formats below). Default is camtNoString

Formats:

HTTP Response

CodeDescription
204The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
X-FILES-CREATEDOnly included in the header when files are generated, returns the number of files generated

Error codes

CodeDescription
err_invalid_paramsParameters are invalid

List Files

Return a list of reconciliation files. The list returned include the files generated since this request was lasted performed. To retrieve files older then this you can add a X-RESET header to reset the feed to an earlier time. The value of the header is in UTC (standard ISO) format.

HTTP Request

GET /creditor/files

copy
curl GET 'https://api.twikey.com/creditor/files' \
--h 'Authorization: ...'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/files',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'Authorization: ...'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
myHeaders.append("Authorization", "...");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/files", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/files")
  .method("GET", null)
  .addHeader("Authorization", "...")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/files");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "...");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Responsecopy
{
    "Files": [
        {
            "name": "sdd_twikey_BE81ZZZ0000000000--201222090211-2223.cod",
            "created": "2021-03-16T17:28:38Z"
        },
        {
            "name": "sdd_twikey_BE81ZZZ0000000000--201222090212-2223.cod",
            "created": "2021-03-16T17:28:38Z"
        }
      ]
  }

HTTP Response

CodeDescription
200The request has succeeded

Error codes

CodeDescription
err_not_authorizedAction not allowed. Contact your customer success agent.

Download File

Download reconciliation files.

HTTP Request

GET /creditor/files/filename

copy
curl GET 'https://api.twikey.com/creditor/files/sdd_twikey_BE81ZZZ0000000000--201222090211-2223.cod' \
--h 'Authorization: .....'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/files/sdd_twikey_BE81ZZZ0000000000-201222090211-2223.cod',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'Authorization: ....'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
myHeaders.append("Authorization", "....");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/files/sdd_twikey_BE81ZZZ0000000000--201222090211-2223.cod", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/files/sdd_twikey_BE81ZZZ0000000000--201222090211-2223.cod")
  .method("GET", null)
  .addHeader("Authorization", "....")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/files/sdd_twikey_BE81ZZZ0000000000--201222090211-2223.cod");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "....");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

Query parameters

NameDescriptionRequiredType
filenamethe filenameYesString

HTTP Response

CodeDescription
200The request has succeeded

Paymentlinks

Paymentlinks can facilitate a one-off payment that needs to happen or can be used in the dunning process. For the latter you don't need to use these endpoints. However if you want to use the former, these endpoints will be of service to you. We don't provide a list endpoint as updates should be retrieved upon receiving a webhook.

Create a payment link for an affiliated customer (via email) or via a name (in which case no customer is linked). It is possible to create a -consolidated- paymentlink by using the parameter txref. Unique transaction references are required! In this case you create a paymentlink linked over several transactions based on their transaction reference. Once paid, every transaction will be marked as paid as well. When specifying a redirectUrl, use http:// or https:// to refer to the website. If an email address is added, the debtor is also added as a customer in Twikey.

HTTP Request

POST /creditor/payment/link

copy
curl -X POST https://api.twikey.com/creditor/payment/link \
  -H 'authorization: **authorization**'\
  -d 'email=no-reply@twikey.com'\
  -d 'message=Faulty payments last month'\
  -d 'amount=100'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/payment/link");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
      "email=no-reply@twikey.com"
    ."&message=Monthly payment"
    ."&amount=10");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
const link = await client.paylink.create({
    ct: Number(CT),
    customerNumber: customerNumber,
    ref: faker.person.firstName(),
    firstname: faker.person.firstName(),
    lastname: faker.person.lastName(),
    message: faker.commerce.productName() + " - " + Date.now(),
    amount: Number(faker.commerce.price({min: 0, max: 1000}))
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void addTransaction(){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("email=no-reply@twikey.com")
            .add("message", "Monthly payment")
            .add("amount", "10")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/payment/link")
            .post(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; //collected through logIn

    public void addTransaction(){
        RestClient client = new RestClient(host + "/creditor/payment/link");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "email=no-reply@twikey.com" +
            "message=Monthly payment" +
            'amount=10"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
  "id": 1,
  "amount": 55.66,
  "msg": "Test",
  "url": "https://mycompany.twikey.com/payment/tr_l2iKz0LT8HvRrmf0"
}

Header X-Purpose:

Use X-Purpose to alter the url for specific use cases. When using the x-purpose qr the generated url is one that can be used to create a qr-code which can be scanned by most bank apps to access the payment page.

In case you want to make the url and QR-code yourself without extra calls, the url is constructed using 'app.twikey.com', your company id and the transaction id, example:
https://app.twikey.com/1234/tr_l2iKz0LT8HvRrmf0

ValueDescription
qrReturns a url that can be scanned in different bank apps.
redirect(default)Returns a branded-url for generic use cases (faster)

Request Parameters

At least a customer number, email or a name is required to be able to create a paymentlink.

NameDescriptionRequiredTypeMax.
customerNumberThe customer number (strongly advised)Nostring50
emailEmail of the debtorNo (Required to send invite)string70
lastnameLast name of the customerNostring50
firstnameFirst name of the customerNostring50
companyNameThe company name (if debtor is a company)Nostring140
cocThe enterprise number (if debtor is a company)Nostring50
lLanguage (en, nl, nl_nl, fr, fr_fr, de, it, pt, es)Nostring5
mobileMobile numberNostring50
ctContract templateNostring
titleMessage to the debtor [*1]Yesstring140
remittancePayment message, if empty then title will be used [*2]Nostring
amountAmount to be billedYesstring
redirectUrlOptional redirect after pay URL. A specific app protocol is required (e.g., http(s)://). Verify that your PSP supports the specific protocol.Nourl
placeOptional placeNostring
expiryOptional expiration dateNodate
sendInviteSend out invite email or SMS directly (email, SMS) [*3]Nostring
addressAddress (street + number)Nostring70
cityCity of debtorNostring50
zipZip code of debtorNostring12
countryISO format (2 letters)Nostring2
txrefReference (ref) from existing unpaid transactionsNostring
methodCircumvents the payment selection with PSP (see supported methods)Nostring
invoiceCreate payment link for a specific invoice numberNostring
isTemplateUse a (custom) payment form [*4]Noboolean
Own attributeCustom attributes defined on your template can also be used.NoAs defined on your template

[1]: This is the message that your customer will see on their bank statement. (More info..)
[2]: This will be the code used in the bank statements Twikey provides that you'll be able to import in your accounting package.
[3]: A registered email can be sent using the value 'registeredEmail' (requires Registered-email integration).
[4]: When set on 'true' the customer is redirected to a (custom) payment form. For more information about template links, please contact support.

supported methods:

language invitations:

The language of the invitation is defined on the language in combination of the country code parameter. It can also be passed explicitly using only the language parameter:

Expiry date:

A timestamp (UTC) can also be included in the expiry date, but this is only supported by certain PSP's. For more information contact our support.

Custom attributes:

Custom attributes defined on your profile can also be used as parameters in the request. This is then also returned in the paymentlink feed/detail request.

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_invalid_amountInvalid Amount given
err_invalid_dateInvalid Expiry Date
err_duplicate_txDuplicate transaction
err_not_foundInvoice not found or invoice amount is exceeded
err_invalid_stateInvoice state is not booked or expired
err_invalid_emailEmail is invalid
err_invalid_nameName is invalid
err_msg_missingTitle is missing
err_no_integrationOne-off payment gateway is not configured on the profile (ct)

Special cases:

Get payment link feed since the last retrieval

HTTP Request

GET /creditor/payment/link/feed

copy
curl -X GET https://api.twikey.com/creditor/payment/link/feed \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/payment/link/feed?all="");
curl_setopt($ch, CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
const feed = await client.paylink.feed(options);
for await (const link of feed) {
    assert.ok(link);
    assert.ok(link.id);
    assert.ok(link.amount);
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void getTransactions(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/payment/link/feed")
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void getTransactions(){
        RestClient client = new RestClient(host + "creditor/payment/link/feed");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
[
    {
        "amount": 17.95,
        "id": 3354,
        "msg": "Betaling Factuur 123",
        "ref": "J7F079OG00000",
        "state": "paid"
    },
    {
        "amount": 19.95,
        "id": 3821,
        "msg": "Rappel Factuur 101",
        "ref": "KJEVMD5",
        "state": "paid"
    }
]

Sideloading customer, meta and time:

Responsecopy
{
    "links": [
        {
            "id": 228192,
            "ct": 2223,
            "amount": 12.19,
            "msg": "Order-id 10058",
            "ref": "R10058",
            "state": "paid",
            "customer": {
                "id": 1010238,
                "email": "email@example.com",
                "firstname": "Smith,",
                "lastname": " Douglas",
                "address": "Stationstraat  43",
                "city": "Gent",
                "zip": "9000",
                "country": "BE",
                "customerNumber": "de8f0be99b1b2521c7a74a5067b5d85f",
                "l": "nl",
                "mobile": null
            },
            "meta": {
                "active": false,
                "sdd": "130542",
                "tx": "255733"
            },
            "time": {
                "creation": "2021-05-03T07:42:40Z",
                "expiration": "2021-05-17T07:42:40Z",
                "lastupdate": "2021-05-03T07:44:00Z"
            }
        },
        {
            "id": 244041,
            "ct": 2223,
            "amount": 449.87,
            "msg": "Invoice February",
            "ref": "invoice-2021-02",
            "state": "paid",
            "customer": {
                "id": 1200553,
                "email": "email@example.com",
                "firstname": "John",
                "lastname": "Smith",
                "address": "Stationstraat 43",
                "city": "Gent",
                "zip": "9000",
                "country": "BE",
                "customerNumber": "5921071",
                "l": "nl",
                "mobile": "+3283838383"
            },
            "meta": {
                "active": false,
                "method": "bancontact",
                "invoice": "d133b08b-fe06-445b-81ff-6846a568d71f"
            },
            "time": {
                "creation": "2021-05-06T15:16:52Z",
                "lastupdate": "2021-05-06T15:16:55Z"
            }
        }
    ]
}

Request Parameters

NameDescriptionRequiredType
allInclude all non-paid updates too (by default only paid updates are returned)Noboolean

include parameter

The 'include' parameter can be added multiple times with different values

ValueDescriptionRequiredType
customercustomer detailNostring
metameta dataNostring
timetime dataNostring
refundsreturn all the refunds done for a payment link

Custom attributes:

Custom attributes defined on your profile can also be returned in the response when passing include=meta. In some case (depending on the PSP), the IBAN used to pay can be captured, if you need to return this, you can add this attribute on your profile to return this in the feed.

Possible states

StatesDescription
CreatedThe payment link has been created but the customer did not do anything with it yet.
StartedThe customer has clicked on the payment link you transmitted him but did not complete the payment. In the Twikey dashboard the state will be shown as clicked.
PendingThe customer started to pay, but did not complete the payment process. (only for iDeal, Paypal & Tikkie)
DeclinedThe customer completed the payment process but the payment wasn't successful
PaidThe customer paid the amount asked.
RefundedThe payment link was refunded
ExpiredThe customer did not complete the payment before the expiration date you defined when creating the payment link.

Response Parameters

meta

Payment links used to pay invoices or direct debit transactions (in case of dunning for example) will include additional meta data. Dependant the case, different response parameters are returned.

ParameterDescription
activethe payment link url is still valid (true, false)
sddinternal sdd identifier (Twikey)
txtransaction id returned when linked to a transaction
methodthe method of payment. Only returned when linked to an invoice or if explicitly passed when creating a paymentlink.
paymentMethodreturns the used payment method when we received it from the payment provider
typethe payment method returned by the PSP. Only returned if we received it (PSP dependant).
invoiceinvoice unique identifier returned when linked to an invoice

time

ParameterDescription
creationcreation of the payment link
expirationexpiry of the payment link. Not returned when there is none
lastupdatelast time the payment link was updated

A payment link can have a state 'active': false when the expiration date is not set or not yet reached. The link is then already invalidated in a different way (paid, archived,..).

HTTP Response

CodeDescription
200The request has succeeded

Error Codes

CodeDescription
err_call_in_progressRequest already in progress by another client

Get status of a payment link

This request is rate limited.

HTTP Request

GET /creditor/payment/link

copy
curl  https://api.twikey.com/creditor/payment/link?id=123 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/payment/link?id=123");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/payment/link?id=123',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void addTransaction(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/payment/link/id=123")
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; //collected through logIn

    public void addTransaction(){
        RestClient client = new RestClient(host + "/creditor/payment/link?id=123");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
[{
   "id": 1,
   "amount": 55.66,
   "msg": "Test",     // title of the paymentlink
   "ref": "My Ref",   // remittance of the paymentlink
   "state": "paid"    // one of created, started, declined, paid, pending, expired
 }]

Query Parameters

Either id or ref is mandatory.

NameDescriptionRequiredType
idId of the linkNostring
refYour referenceNostring

Include meta

You can use the additional query parameter include=meta to return additional information about the payment link.

Payment links used to pay invoices or direct debit transactions (in case of dunning for example) will include additional meta data. Dependant the case, different response parameters are returned:

ParameterDescription
activethe payment link url is still valid (true, false)
sddinternal sdd identifier (Twikey)
txtransaction id returned when linked to a transaction
methodthe method of payment. Only returned when linked to an invoice or if explicitly passed when creating a paymentlink.
paymentMethodreturns the used payment method when we received it from the payment provider
typethe payment method returned by the PSP. Only returned if we received it (PSP dependant).
invoiceinvoice unique identifier returned when linked to an invoice
timereturn date and time events (creation, last updated, expiry)

Include refunds

You can use the additional query parameter include=refunds to return all refunds done for a payment link.
It is possible to combine different includes at once, example: /creditor/payment/link?id=123&include=meta&include=refunds

Possible states

StatesDescription
createdThe payment link has been created but the customer did not do anything with it yet.
startedThe customer has clicked on the payment link you transmitted him but did not complete the payment. In the Twikey dashboard the state will be shown as clicked.
pendingThe customer started to pay, but did not complete the payment process. (only for iDeal, Paypal & Tikkie)
declinedThe customer completed the payment process but the payment wasn't successful
paidThe customer paid the amount asked.
expiredThe customer did not complete the payment before the expiration date you defined when creating the payment link.

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Error codes

CodeDescription
err_missing_paramsNo parameter given
err_no_transactionNo link found based on the transaction
err_not_foundNo link found

Refund the full or partial amount of a payment link.

copy
curl -x POST 'https://api.twikey.com/creditor/payment/link/refund' \
  -H 'authorization: **authorization**'\
  -D 'id=123' \
  -D 'message=refund' \
  -D 'amount=25'

HTTP Request

POST /creditor/payment/link/refund

Supported PSP

NameSupported
MollieYes
MultiSafePayYes
BuckarooYes
CCVYes **[1]*

Request Parameters

NameDescriptionRequiredType
idPaymentlink IDYesstring
messageRefund messageYesString
amountFull or partial amount (if not passed, full amount is used)Nonumber
copy
{
    "id": 10.23,
    "amount": 25.0,
    "msg": "refund"
}

HTTP Response

CodeDescription
200The request has succeeded

Error codes

CodeDescription
err_contact_supportError details in response "extra"
err_fail_refundError details in response "extra"

Remove a payment link. Only paymentlinks with status 'created' can be removed, all other ones will be archived.

copy
curl -X DELETE https://api.twikey.com/creditor/payment/link?id=123 \
  -H 'authorization: **authorization**'

HTTP Request

DELETE /creditor/payment/link

Request Parameters

NameDescriptionRequiredType
idpaymentlink IDYes)string

Meta data

Including meta data when you retrieve the status of one (or more) payment link(s) provides you with the following information:

Sample:

Responsecopy
{
    "Links": [
        {
            "id": 77920,
            "ct": 2223,
            "amount": 6.0,
            "msg": "123456789123",
            "ref": "CF933-20200807073818839144-0",
            "state": "created",
            "meta": {
                "sdd": "63884",
                "tx": "1234567"
            }
        },
        {
          "id": 77955,
          "ct": 2223,
          "amount": 10.0,
          "msg": "124512454",
          "ref": "CF933-202008070738447439143-0",
          "state": "created",
          "meta": {
            "tx": "1234567"
          }
        },
        {
            "id": 76667,
            "ct": 2223,
            "amount": 100.0,
            "msg": "123456789123",
            "ref": null,
            "state": "paid",
            "meta": {
                "invoice": "a48c77a7-349e-424b-bdfc-baa748ee9e55"
            }
        }
    ]
}

Meta parameters

NameDescription
txThe transaction for which this link was generated
sddThe sdd transaction (state = failed) send to the bank for wich this link was generated
invoiceThe invoice uuid linked to the payment link

While the tx can be returned if the link was generated on a transaction, the sdd will only be returned in the context of dunning. To retrieve related transaction, use the returned 'tx' id in the transaction status request.

Refunds (Credit Transfer)

Refunds can be used to completely or partially refund a transaction to a debtor. In exceptional cases, you may have to transfer funds from one account to another. This could be the case when you collected money that should be transferred to another person/company.

If the customer was not known, you may need to create a beneficiary account prior to making this call. In order to avoid extra costs on bank side an additional call to create the batch may be necessary or can be done automatically upon request.

Create/add a new credit transfer

Create or add a new credit transfer. If the customer has a signed mandate or beneficiary account(s) you can create a refund directly. When this is not the case you will need to add a beneficiary account first.

The customer has a signed mandate

Then you can pass the parameter 'customerNumber' without IBAN in the request. The IBAN account from the last signed mandate is used as beneficiary account to refund the funds to.

The customer has a beneficiary account

In this case you only need to pass the 'iban' or 'customerNumber' parameter in the request. This will directly add the refund to that beneficiary account. It is strongly advised to use 'customerNumber' and 'iban' parameters together should you have multiple customers with the same iban account or a customer with multiple beneficiary accounts. Otherwise, we don't know for which customer or on which account the refund is and will add it on a beneficiary account found for that iban or customer.

HTTP Request

POST /creditor/transfer

copy
curl https://api.twikey.com/creditor/transfer \
  -H 'authorization: authorization' \
  -d 'iban=BE68068897250734' \
  -d 'customerNumber=123' \
  -d 'message=test%20credit%20transfer' \
  -d 'ref'='123' \
  -d 'amount='0.00'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "iban=BE68068897250734"
    ."&customerNumber=123"
    ."&message=test credit transfer"
    ."&amount="50.00"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: {
            "iban":"BE680688972.....",
            "customerNumber":"123",
            "message":"test credit transfer",
            "amount":"50.00"
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
            .add("iban", "BE680688972.....")
            .add("customerNumber","123")
            .add("message","test credit transfer")
            .add("amount", "50.00")
            .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/transfer")
          .post(formBody)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfer");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter("application/x-www-form-urlencoded",
            "iban=BE68068897250734" +
            "&customerNumber=123" +
            "&message=test credit transfer" +
            "&amount=50.00"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Entries": [
        {
            "id": "11DD32CA20180412220109485",
            "iban": "BE68068097250734",
            "bic": "JVBABE22",
            "amount": 12,
            "msg": "test",
            "place": null,
            "ref": "123",
            "date": "2018-04-12"
        }
    ]
}

Request Headers

NameDescriptiontypemax. length
Idempotency-KeyUnique key usable only once per request every 24hrsstring64

Request parameters

NameDescriptionRequiredType
customerNumberThe customer number (strongly recommended)No (if iban is passed)string
ibanIban of the beneficiary (must be active)No (if customerNumber is passed)string
messageMessage to the creditorYesstring (140)
amountAmount to be refundedYesnumber
refReference of the transactionNoString
dateRequired execution date of the transaction (ReqdExctnDt)Nostring
placeOptional placeNostring

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
409Conflict. Idempotency-key already used

Error codes

CodeDescription
err_invalid_ibanInvalid Iban
err_invalid_stateMandate existed, but was not active
err_invalid_dateInvalid date
err_invalid_sepacharsInvalid message
err_invalid_amountInvalid amount
err_not_foundCustomer not found
err_invalid_paramsSome parameter is invalid, see the extra in the response body for more information.
err_duplicate_refIdempotency-key was already used in the last 24hrs.

Get credit transfer feed

Retrieve list of credit transfers that have changes since the last call. Only when the credit transfers is in a PAID status it is returned in the feed.

copy
curl https://api.twikey.com/creditor/transfer \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; // collected through login

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer");
curl_setopt($ch, CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, // collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login
    public void getTransactions(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transfer")
            .get()
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // collected through login

    public void getTransactions(){
        RestClient client = new RestClient(host + "/creditor/transfer");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

HTTP Request

GET /creditor/transfer

Responsecopy
{
    "Entries": [
        {
            "id": "IBWICD6E44Y1N3YRMCVB2U7BA",
            "iban": "BE70361272935897",
            "bic": "GDOENFY",
            "amount": 1000,
            "msg": "credit transfer message",
            "place": null,
            "ref": "123",
            "date": "2017-01-20",
            "state": "PAID",
            "bkdate": "2017-01-24"
        },
        {
            "id": "X52D04L9DNNPWKCRGBV7YPV2R",
            "iban": "BE93261223700539",
            "bic": "BRODBZL",
            "amount": 12345,
            "msg": "credit transfer message",
            "place": "Brugge",
            "ref": "456",
            "date": "2018-01-23",
            "state": "PAID",
            "bkdate": "2018-01-25"
        },
        {
            "id": "ZE31QPMT6GRMHEY7IPOOD219R",
            "iban": "BE4447226747140",
            "bic": "ORNSRAM",
            "amount": 250099,
            "msg": "",
            "place": "Ghent",
            "ref": "789",
            "date": "2017-03-23",
            "state": "PAID",
            "bkdate": "2017-03-24"
        },
        "..."
    ]
}

Query Parameters

ParameterDescription
include=seqReturn the sequence number in the feed for each entry.

HTTP Response

CodeDescription
200The request has succeeded

Details of a credit transfer

Retrieve the details of a credit transfer by id. The id is the e2e id of the credit transfer upon creating the credit transfer. You can find the E2E in the Twikey interface by going to the customer and opening the Refunds tab. The E2E is 'Your reference'.

Depending on the bank used for a refund batch, the response value of bkdate can be a date or date with timestamp.

This request is rate limited.

HTTP Request

GET /creditor/transfer/detail

copy
curl https://api.twikey.com/creditor/transfer/detail?id=609C16C920180919083905923 \
  -H 'authorization: authorization'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer/detail?id=609C16C920180919083905923");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer/detail?id=609C16C920180919083905923',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");

        Request request = new Request.Builder()
          .url(host + "/creditor/transfer/detail?id=609C16C920180919083905923")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfer/detail?id=609C16C920180919083905923");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Entries": [
        {
            "id": "609C16C920180919083905923",
            "iban": "BE08001166979213",
            "bic": "GEBABEBB",
            "amount": 5,
            "msg": "test",
            "place": null,
            "ref": "123",
            "date": "2018-09-19",
            "state": "PAID",
            "bkdate": "2017-03-24"
        }
    ]
}

Query parameters

NameDescriptionRequiredType
idid (e2e identifier) of the created refundYesstring

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)
429Too many requests

Error codes

CodeDescription
err_no_transactionNo such transaction

Remove a credit transfer

Remove a single credit transfer.

HTTP Request

DELETE /creditor/transfer

copy
curl -X DELETE https://api.twikey.com/creditor/transfer?id=123 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer?id=123");
curl_setopt($ch, CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer?id=123',
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void removeTransaction(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transfer?id=123")
            .delete(null)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void removeTransaction(){
        RestClient client = new RestClient(host + "/creditor/transfer?id=123");
        RestRequest request = new RestRequest(Method.DELETE);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

Query parameters

NameDescriptionRequiredType
idid of the created refundYesstring

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_transactionNo such transaction

Batch creation

Once the credit transfers are created, you need to create the batch with refunds to send to the bank. A batch can be created based upon the account of the recurring gateway configured on an existing profile or, any other bank gateway account available in your Payment Hub (Refunds need to be enabled for that gateway).

After the creation of the batch, the file needs to be processed in your bank environment. For some banks we can do the upload of the batch in your bank environment, for other banks you will need to download the batch from the Twikey dashboard via the menu Refunds. A refund must always be signed extra in your bank environment.

HTTP Request

POST /creditor/transfer/complete

copy
curl https://api..twikey.com/creditor/transfer/complete \
  -H 'authorization: authorization' \
  -d 'ct=**ct_id**'
copy
$host = "https://api.twikey.com";
$ct = "**ct_id**";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer"/complete);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "ct=$ct");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    ct= "**ct_id**",
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer/complete',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: {
            "ct": ct
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String ct = "**ct_id**";
    private String authorisation = null; //collected through logIn

    public void completeCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
            .add("ct", ct)
            .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/transfer/complete")
          .post(formBody)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void completeCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfer/complete");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter(
            "application/x-www-form-urlencoded",
            "ct=" + ct,
            ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

Query parameters

NameDescriptionRequiredType
ctProfile containing the originating account (configured as recurring gateway)Yesstring
ibanOriginating account, if different from ct account (any other bank account with refunds enabled from your Payment Hub)Nostring
Responsecopy
{
    "CreditTransfers": [
        {
            "id": 2837,
            "pmtinfid": "Twikey-20220330113125070605075",
            "entries": 2
        }
    ]
}

HTTP Response

CodeDescription
200The request has succeeded
400Invalid request

Batch details

Get all details from a specific batch

This request is rate limited.

HTTP Request

GET /creditor/transfer/complete

copy
curl https://api..twikey.com/creditor/transfer/complete?id=2837&pmtinfid=Twikey-20220330113125070605075 \
  -H 'authorization: authorization' \
copy
$host = "https://api.twikey.com";
$id = "**id**";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer/complete?id=123");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    ct= "**ct_id**",
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfer/complete?id=123',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void completeCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        Request request = new Request.Builder()
          .url(host + "/creditor/transfer/complete?id=123")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void completeCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfer/complete?id=123");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        IRestResponse response = client.Execute(request);
    }
}

Query parameters

NameDescriptionRequiredType
idId of the batch previously submittedNostring
pmtinfidPmtInfId of the batch previously submittedNostring
Responsecopy
{
  "CreditTransfers": [
    {
      "id": 2837,
      "pmtinfid": "Twikey-20220330113125070605075",
      "progress": "download",
      "entries": 2
    }
  ]
}

Response Parameters

NameDescription
idBatch identifier
pmtinfidPayment identifier
progressprogress of the batch. received (by bank), download
entriescredit transfers in this batch

HTTP Response

CodeDescription
200The request has succeeded
400Invalid request

Error codes

CodeDescription
err_invalid_paramsNo such batch available

Get beneficiary accounts

Fetch a list of all customers' beneficiary accounts.

HTTP Request

GET /creditor/transfers/beneficiaries

copy
curl https://api.twikey.com/creditor/transfers/beneficiaries?withAddress=true \
  -H 'authorization: authorization'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfers/beneficiaries?withAddress=true");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfers/beneficiaries?withAddress=true',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");

        Request request = new Request.Builder()
          .url(host + "/creditor/transfers/beneficiaries?withAddress=true")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfers/beneficiaries?withAddress=true");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "beneficiaries": [
        {
            "name": "sdfsf",
            "iban": "BE92221216720939",
            "bic": "GEBABEBB",
            "available": true,
            "address": null
        },
        {
            "name": "beneficiary2",
            "iban": "BE16645348971174",
            "bic": "JVBABE22",
            "available": true,
            "address": {
                "street": "Veldstraat 11",
                "city": "Gent",
                "zip": "9000",
                "country": "BE"
            }
        }
    ]
}

Query parameters

NameDescriptionRequiredType
withAddressinclude addresses in reponseYesboolean

HTTP Response

CodeDescription
200The request has succeeded
500System error

Add a beneficiary account

In order to be able to do a refund, one needs to have a beneficiary account. The account can be either created explicitly via this call or implicitly via a signed SDD mandate. In the latter case, the account from the mandate is taken so you can immediately call the refund without this call.

Creating a beneficiary account can be done for an existing customer or a new one. This is done based on the customerNumber or in its absence the email address. If a customer is found, the address will also be updated if address, zip, city and country are passed on in the call.

For new customers and therefor new accounts, it is strongly advised to add the customerNumber as it allows you to reference them later for updates or creation of other objects such as contracts/mandate and/or paymentlinks. An address and name are mandatory.

HTTP Request

POST /creditor/transfers/beneficiaries

copy
curl -X POST https://api.twikey.com/creditor/transfers/beneficiaries \
  -H 'authorization: authorization' \
  -d 'customerNumber=123' \
  -d 'email=support@twikey.com' \
  -d 'name=Support Twikey' \
  -d 'l=NL' \
  -d 'mobile=32479123123' \
  -d 'address=Stationstraat 43' \
  -d 'zip="9051"' \
  -d 'city=Sint Denijs Westrem' \
  -d 'country=BE' \
  -d 'iban=BE68068897250734' \
  -d 'bic=JVBABE22'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfers/beneficiaries");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,
    "&email=support%40twikey.com"
    ."&customerNumber=123"
    ."&name=Support Twikey"
    ."&l=NL"
    ."&address=Stationstraat%2043"
    ."&zip=9051"
    ."&city=Sint%20Denijs%20Westrem"
    ."&country=BE"
    ."iban=BE68068897250734"
    ."bic=JVBABE22"
);
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfers/beneficiaries',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: {
            "customerNumber": "134",
            "email": "support@twikey.com",
            "name": "Support Twikey",
            "l": "NL",
            "address": "Stationstraat 43",
            "zip": "9051",
            "city": "Sint Denijs Westrem",
            "country": "BE",
            "iban": "BE68068897250734",
            "bic": "JVBABE22"
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
            .add("customerNumber", "134")
            .add("email", "support@twikey.com")
            .add("name", "Support Twikey")
            .add("l", "NL")
            .add("address", "Stationstraat 43")
            .add("zip", "9051")
            .add("city", "Sint Denijs Westrem")
            .add("country", "BE")
            .add("iban","BE68068897250734")
            .add("bic", "JVBABE22")
            .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/transfers/beneficiaries")
          .post(formBody)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/transfers/beneficiaries");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter("application/x-www-form-urlencoded",
            "&customerNumber=123" +
            "&email=support%40twikey.com" +
            "&name=Support Twikey" +
            "&l=NL" +
            "&address=Stationstraat%2043" +
            "&zip=9051" +
            "&city=Sint%20Denijs%20Westrem" +
            "&country=BE" +
            "iban=BE68068897250734" +
            "bic=JVBABE22"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "name": "Beneficiary Name",
    "iban": "BE68068897250734",
    "bic": "JVBABE22",
    "available": true,
    "address": {
        "street": "Veldstraat 11",
        "city": "Gent",
        "zip": "9000",
        "country": "BE"
    }
}

Query parameters

NameDescriptionRequiredTypeMax.
customerNumberThe customer numberNostring50
nameFirstname & lastname of the debtorNostring
emailEmail of the debtorNostring70
llanguage of the customer ISO format (2 letters)Nostring
mobileMobile number required for sms (International format +32499123445)Nostring50
addressThe street name and number/boxNo (Yes for new customers)string70
cityCity of debtorNo (Yes for new customers)string50
zipZipcode of debtorNostring12
countryISO format (2 letters)No (Yes for new customers)string2
companyNameThe company nameNostring140
vatnoThe enterprise numberNostring50
ibanIBAN of the beneficiaryYesstring35
bicBIC of the beneficiaryNostring11

HTTP Response

CodeDescription
200The request has succeeded
400Validation error due to wrong user input

Error codes

CodeDescription
err_name_requiredInvalid or missing name
err_invalid_ibanInvalid IBAN number
err_invalid_bicInvalid BIC number
register_address_missingAddress missing

Disable a beneficiary account

Disable of a beneficiary account can be done via an IBAN number, but it's strongly advisable to add the customerNumber in the call. This is optional for backwards compatible reasons, but strongly recommended as an account may be used on multiple customers. Note that the the beneficiary will be disabled and not deleted. Reactivating a beneficiary account can only be done using the interface.

HTTP Request

DELETE /creditor/transfers/beneficiaries/{IBAN}?customerNumber={customerNumber}

copy
curl -X DELETE https://api.twikey.com/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123 \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123");
curl_setopt($ch, CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, 'Authorization : $authorization");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123',
        method: 'DELETE',
        headers: {
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void removeTransaction(){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(host + "/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123")
            .delete(null)
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; // gathered through logIn

    public void removeTransaction(){
        RestClient client = new RestClient(host + "/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123");
        RestRequest request = new RestRequest(Method.DELETE);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("Authorization", authorization);

        IRestResponse response = client.Execute(request);
    }
}

HTTP Response

CodeDescription
204the request has succeeded, no content
400bad request
404the entity was not found

Error codes

CodeDescription
err_not_foundNo such beneficary
err_invalid_ibanNo beneficary found for that IBAN
err_fail_refundBeneficiary account is disabled

Issuers

Some requests require to pass a BIC code to identify the bank. With this request you can fetch the banks connected to Emachtiging, iDIN

Fetch connected banks

Return the banks that are currently connected to iDIN and Emachtiging. For Emachtiging CORE banks are returned by default. A 'type' parameter can be passed to return B2B supported banks.

HTTP Request

GET /creditor/issuers/<method>

copy
curl https://api.twikey.com/creditor/issuers/emachtiging \
  -H 'authorization: authorization'
copy
$host = "https://api.twikey.com";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/issuers/emachtiging");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/issuers/emachtiging',
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String authorisation = null; //collected through logIn

    public void createCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        Request request = new Request.Builder()
          .url(host + "/creditor/issuers/emachtiging")
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com"
        authorisation = null; // collected through logIn

    public void createCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/issuers/emachtiging");
        RestRequest request = new RestRequest(Method.GET);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
[
    {
        "Bic": "ABNANL2A",
        "Name": "ABN AMRO"
    },
    {
        "Bic": "ASNBNL21",
        "Name": "ASN"
    }
]

Method

Replace by one of the currently supported methods:

Query parameters

NameDescriptionRequiredType
typeonly for method emachtiging. 'core' (default) or 'b2b'Nostring

HTTP Response

CodeDescription
200The request has succeeded
400bad request

Customer

Update a customer

Update a customer via this request. It is not possible create a new customer or to merge customers using this request.

copy
  curl PATCH 'https://api.twikey.com/creditor/customer/{customerNumber}?
  firstname=John
  &lastname=Doe
  &address=Highstreet 66
  &city=Gent
  &zip=9000
  &country=BE
  &l=nl
  &email=mycustomer@example.com
  &mobile=+32488995577
  &ct=2223
  &customerNumber=newCustomerNumber123' \
--h 'Authorization: .....'
copy
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/customer/{customerNumber}?firstname=John&lastname=Doe&address=Highstreet%2066&city=Gent&zip=9000&l=NL&email=mycustomer@example.com&mobile=+32488995577&ct=2223&customerNumber=newCustomerNumber123',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'PATCH',
  CURLOPT_HTTPHEADER => array(
    'Authorization: ....'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
myHeaders.append("Authorization", "....");

var urlencoded = new URLSearchParams();

var requestOptions = {
  method: 'PATCH',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/customer/{customerNumber}?firstname=John&lastname=Doe&address=Highstreet 66&city=Gent&zip=9000&lang=NL&email=mycustomer@example.com&mobile=+32488995577&ct=2223&ref=newCustomerNumber123", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
        .build();
        MediaType mediaType = MediaType.parse("text/plain");
        RequestBody body = RequestBody.create(mediaType, "");
        Request request = new Request.Builder()
        .url("https://api.twikey.com/creditor/customer/{customerNumber}?firstName=John&lastname=Doe&address=Highstreet 66&city=Gent&zip=9000&l=NL&email=mycustomer@example.com&mobile=+32488995577&ct=2223&customerNumber=newCustomerNumber123")
        .method("PATCH", body)
        .addHeader("Authorization", ".....")
        .build();
        Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/customer/11233?firstname=John&lastname=Doe&address=Highstreet 66&city=Gent&zip=9000&l=NL&email=mycustomer@example.com&mobile=+32488995577&ct=2223&customerNumber=newCustomerNumber123");
client.Timeout = -1;
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", ".....");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

HTTP Request

PATCH https://api.twikey.com/creditor/customer/{customerNumber}

Query Parameters

NameDescriptionRequiredTypeMax.
ctDefault templateNointeger4
lLanguage (en/fr/nl/de/pt/es/it)Nostring2
customerNumberUpdate the customer numberNostring50
emailEmail of the customerNostring70
lastnameLastname of the customerNostring50
firstnameFirstname of the customerNostring50
mobileMobile number required for sms (International format 0032499123445)Nostring50
addressAddress (street + number)Nostring70
cityCity of customerNostring50
zipZipcode of customerNostring50
countryISO formatNostring2
companyNameThe company name (if customer is company)Nostring140
cocThe enterprise number. Only updated if the company name is also changedNostring50

HTTP Response

CodeDescription
204Request has succeeded
400Request failed

Error codes

CodeDescription
err_invalid_paramsInvalid parameters
err_invalid_countryCountry code invalid
err_invalid_emailEmail invalid
err_invalid_langLanguage code invalid
err_debtor_not_foundcustomer not found
err_duplicate_refthe customer number already exist

Other

Rate limits

As we have a far better alternative to querying a single object (feed) sometimes it's necessary to fetch the info about a particular item. Even though it might be true at that particular time. But to ensure these are one-offs and that you don't implement something that won't scale we added rate limits to those requests.

For rate-limited calls we included the response headers in both the request and our documentation:

HeaderDescription
X-Rate-Limit-RemainingThe number of request remaining for each minute
X-Rate-Limit-Retry-After-SecondsReturned once the rate limit is reached. Time (in seconds) before a new request can be made

Blacklist account

When there is (suspected) abuse or another valid reason, you can add bank accounts to a blacklist. Bank accounts listed can't be used to register new mandates, existing mandates registered with this iban can be suspended or cancelled. The option to notify the customer will trigger the standard email informing the customer that their mandate was suspended, reactivated or cancelled.

Blacklisting is available in the Enterprise subscription.

When adding a bank account to the blacklist a parameter can be passed to change the state of all the mandates registered with this iban. "passive": The mandate is suspended. "cancel": The mandate is cancelled.

copy
curl -X POST 'https://api.twikey.com/creditor/ibanblacklist' \
--h 'Authorization: {{authorization}}' \
--d' rsn=IBAN blacklisted' \
--d 'iban=BE123456789123456' \
--d 'notify=false' \
--d 'state=passive' \

response

 "iban": "BE123456789123456",
    "rsn": "IBAN blacklisted",
    "state": "passive",
    "updatedMandates": [
        {
            "mndtId": "MNDT123"
        }
                        ]
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/ibanblacklist',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => 'rsn=IBAN%20blacklisted&iban=BE123456789123456&notify=false&state=passive',
  CURLOPT_HTTPHEADER => array(
    'Authorization: $authorization'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

HTTP Request

POST https://api.twikey.com/creditor/ibanblacklist

Query Parameters

NameDescriptionRequiredType
ibanbank account to add to the blacklistYesstring
rsnreasonYesstring
stateupdate all mandates state registered with this iban (passive, cancel)Nostring
notifynotify the customer by emailNoboolean

HTTP Response

CodeDescription
200Request succeeded
400Invalid or missing parameter(s)

Error codes

CodeDescription
err_provide_reasonReason is missing
err_invalid_paramsinvalid or missing parameter

List blacklisted accounts

Retrieve a list of all the bank accounts currently blacklisted.

copy
curl GET 'https://api.twikey.com/creditor/ibanblacklist' \
--h 'Authorization: {{authorization}}'
Responsecopy
[
    {
        "iban": "BE123456789123456",
        "rsn": "IBAN blacklisted"
        },
    {
        "iban": "BE33445566889988",
        "rsn": "Suspected abuse"
    }
]
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/ibanblacklist',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_HTTPHEADER => array(
    'Authorization: $authorization'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
var authorization = '.......';

myHeaders.append("Authorization", authorization);

var urlencoded = new URLSearchParams();

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/ibanblacklist", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/ibanblacklist")
  .method("GET", null)
  .addHeader("Authorization", ".......")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/ibanblacklist");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "........");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);

HTTP Request

GET https://api.twikey.com/creditor/ibanblacklist

HTTP Response

CodeDescription
200Request succeeded

Remove blacklisted account

Remove prior added accounts from the blacklist. Suspended mandates can be reactivated using this request.

copy
curl -X DELETE 'https://api.twikey.com/creditor/ibanblacklist' \
--h 'Authorization: {{authorization}}' \
--d 'iban=BE123456789123456' \
--d 'state=active' \
--d 'notify=false'
copy
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.twikey.com/creditor/ibanblacklist?iban=BE123456789123456&state=active&notify=false',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'DELETE',
  CURLOPT_HTTPHEADER => array(
    'Authorization: $authorization'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;
copy
var myHeaders = new Headers();
myHeaders.append("Authorization", "......");

var urlencoded = new URLSearchParams();

var requestOptions = {
  method: 'DELETE',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

fetch("https://api.twikey.com/creditor/ibanblacklist?iban=BE123456789123456&state=active&notify=false", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
copy
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
  .url("https://api.twikey.com/creditor/ibanblacklist?iban=BE123456789123456&state=active&notify=false")
  .method("DELETE", body)
  .addHeader("Authorization", "......")
  .build();
Response response = client.newCall(request).execute();
copy
var client = new RestClient("https://api.twikey.com/creditor/ibanblacklist?iban=BE123456789123456&state=active&notify=false");
client.Timeout = -1;
var request = new RestRequest(Method.DELETE);
request.AddHeader("Authorization", "......");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Responsecopy
{
  "iban": "BE123456789123456",
  "state": "active",
  "updatedMandates": [
    {
      "mndtId": "MNDT123"
    }
  ]
}

HTTP Request

DELETE https://api.twikey.com/creditor/ibanblacklist

Query Parameters

NameDescriptionRequiredType
ibanbank account to remove from the blacklistYesstring
statereactivate all suspended mandates using this iban (active)Nostring
notifynotify the customer by emailNoboolean

HTTP Response

CodeDescription
200Request succeeded
400Invalid or missing parameter(s)

Error codes

CodeDescription
err_invalid_paramsinvalid or missing parameter

Account reporting

Get account reporting, this endpoint allows you to retrieve a stream of items listed on your account as soon as they're made available from the bank.

Returns all reporting since the last call.

HTTP Request

GET /creditor/reporting

copy
curl -X GET https://api.twikey.com/creditor/reporting \
  -H 'authorization: **authorization**'
copy
$host = "https://api.twikey.com";
$authorization = null; // collected through login

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/reporting");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, // collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/reporting',
        method: 'GET',
        headers: {
            'Authorization': authorization
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login
    public void updateTransaction(){
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
            .url(host + "/creditor/reporting")
            .get()
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; // collected through login

    public void updateTransaction(){
        RestClient client = new RestClient(host + "/creditor/reporting");
        RestRequest request = new RestRequest(Method.PUT);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("Authorization", authorization);
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "Statements": [
        {
            "id": 146406841,
            "type": "PMNT/RCDT/ESCT",
            "date": "2018-11-20",
            "amount": 1258,
            "iban": "BE01234567890123",
            "bic": "GEBABEBB",
            "msg": "Testing",
            "mndtId": "TST123",
            "party": "Koen BVBA"
        },
          {
            "id": 262017,
            "type": "PMNT/RDDT/PRDD",
            "date": "2022-01-19",
            "amount": -2500,
            "iban": "BE01234567890234",
            "bic": "GKCCBEBB",
            "msg": "order 1235",
            "meta": {
              "rc": "AM04",
              "e2e": "2265B-20220119134125753882-0"
            },
            "party": "Wouters Doe"
          }
    ]
}

Response Parameters

ParameterDescription
amountamount in cents [*1]
meta.rcError code in case of a failed collection of the payment
meta.e2eBank reference
partyName of the customer

[*1]: When the collection failed the amount is negative

HTTP Response

CodeDescription
200The request has succeeded

Retrieve Identification url

Identification can be used to identify a customer using a third party (bank, government, ..). When using this request, a url to their services is generated that can be used by the customer. A template of type 'IDENT' is requested and a specific signing method. Currently supported signing methods are iDIN (Netherlands) and itsme (Belgium).

Retrieve direct link from the bank

HTTP Request

POST /creditor/ident

copy
curl https://api..twikey.com/creditor/ident \
  -H 'authorization: authorization' \
  -d 'ct=**ct_id**'
copy
$host = "https://api.twikey.com";
$ct = "**ct_id**";
$authorisation = null; //collected through login

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, "$host/creditor/ident");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "ct=$ct&bic=ABNANL2A");
curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization");

$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    ct= "**ct_id**",
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through login
    options = {
        host: host,
        port: '443',
        path: '/creditor/ident',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        data: {
            "ct": ct,
            "bic": 'ABNANL2A'
        }
    };

var req = https.request(options, function (res) {
        console.log("response: " + res)
});
copy
public class TwikeyApi{
    private String host = "https://api.twikey.com";
    private String ct = "**ct_id**";
    private String authorisation = null; //collected through logIn

    public void completeCreditTransfer(){
        OkHttpClient client = new OkHttpClient();

        MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
        RequestBody formBody = new FormBody.Builder()
            .add("ct", ct)
            .add("bic","ABNANL2A")
            .build();

        Request request = new Request.Builder()
          .url(host + "/creditor/ident")
          .post(formBody)
          .addHeader("content-type", "application/x-www-form-urlencoded")
          .addHeader("authorization", authorisation)
          .addHeader("cache-control", "no-cache")
          .build();

        Response response = client.newCall(request).execute();
    }
}
copy
public class TwikeyAPI {
    private  String host ="https://api.twikey.com",
        ct = "**ct_id**",
        authorisation = null; // collected through logIn

    public void completeCreditTransfer(){
        RestClient client = new RestClient(host + "/creditor/ident");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("authorization", authorisation);
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddParameter(
            "application/x-www-form-urlencoded",
            "ct=" + ct,
            "bic=ABNANL2A",
            ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}

Query parameters

NameDescriptionRequiredType
ctTemplate containing the originating accountYesstring
bicBIC Code of the bank to retrieve a url for (Idin only)Yesstring
methoditsme or idinYesstring
lLanguageNostring

HTTP Response

CodeDescription
200The request has succeeded

Error Codes

CodeDescription
err_bank_t_oTime out at the bank
err_idin_failFailure at the bank

Transaction reservations

Validates if a transaction is eligible for creation and returns a reservation code that can be used to create the actual transaction. The transaction is evaluated against your configured risk rules.

To create a reservation:

NameDescriptionRequiredTypeMax.
reservationIndicates this is a reservation (should contain true)Yesboolean4
reservationMinimumOptional value, which if passed allows Twikey to reserve the highest amount lower than the amount but higher than this valueNodecimal
reservationExpirationOptional date after which this reservation automatically is voidedNodate

To create a transaction for the reservation:

HTTP Request

POST /creditor/reservation

copy
curl -X POST https://api.twikey.com/creditor/reservation \
  -H 'authorization: **authorization**'\
  -d 'reservation=true' \
  -d 'mndtId=mndtId123' \
  -d 'message=Monthly payment' \
  -d 'amount=10'

Successful response (status=200):

Responsecopy
{
    "id":"2e708d7a-a4bf-4535-aea2-3ab24c179519",
    "mndtId":"MYMANDATE123",
    "reservedAmount":50.00,
    "expires":"2021-12-07T22:42:47Z"
}

Or when a risk rule was hit (status=400):

Responsecopy
{
    "code": "err_billing_overdrawn",
    "message": "Maximum amount reached",
    "over_amount": "50.00",
    "rule_amount": "400.00",
    "rule": "Limit400euro",
    "tx_amount": "125.00"
}

When a new transaction can't be created due to configured risk rules, the details are returned in the response.

CodeDescription
err_billing_overdrawnMaximum amount/number of transactions reached according to risk rules
rule_amountMaximum amount/number of transactions defined in the risk rule
over_amountAmount/number of transactions exceeding the risk rule

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_contractNo mandate found
err_invalid_stateThe mandate is not active
err_invalid_dateInvalid Date
err_invalid_sepacharsInvalid characters in message to debtor
err_invalid_amountInvalid Amount given
err_not_authorisedReservation already exist for this mandate

Delete a reservation

To delete a reservation you have two methods.

  1. Use this endpoint to first delete the reservation and then create a new one
  2. Create a new reservation for the same mandate and pass the parameter force=true. This wil delete the old reservation and create a new one in the same request.

HTTP Request

DELETE /creditor/reservation

copy
curl -X POST https://api.twikey.com/creditor/reservation \
  -H 'authorization: **authorization**'\
  -H 'X-RESERVATION: **reservation id**'

HTTP Response

CodeDescription
204The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_not_foundReservation ID not found

Get payment methods

Retrieve the payment methods activated on a profile under 'Invoices' > 'Payment methods' via this request.

HTTP Request

GET /creditor/payment/methods

Query Parameters

NameDescriptionRequiredType
ctprofile idYesnumber
copy
curl -X GET https://api.twikey.com/creditor/payment/methods?ct=1234 \
  -H 'authorization: **authorization**'\

Successful response (status=200):

Responsecopy
[
  "bancontact",
  "maestro",
  "mastercard",
  "visa"
]

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_such_ctProfile not found

IBAN-Name Check

The IBAN-Name Check allows you to verify account details registered at the bank of origin. When a response is returned, it is wat is registered at the bank-side, not in your Twikey environment.

Two integrations are possible, one for the Netherlands and one for Italy.

HTTP Request

POST /creditor/ibancheck

copy
curl -X POST https://api.twikey.com/creditor/ibancheck \
  -H 'authorization: **authorization**'\
  -d 'name=Doortje Doorzon' \
  -d 'iban=NL51ABNA0577013939'
copy
$host = "https://api.twikey.com";
$authorization = null; /collected through logIn

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$host/creditor/ibancheck");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,"name=Doortje Doorzon"
    ."&iban=NL51ABNA0577013939");
$server_output = curl_exec ($ch);
curl_close ($ch);
copy
var https = require('https'),
    querystring = require('querystring'),
    host = "api.twikey.com",
    authorization = null, //collected through logIn
    options = {
        host: host,
        port: '443',
        path: '/creditor/ibancheck',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': authorization
        },
        body :{
            'name' : 'Doortje Doorzon',
            'iban': 'NL51ABNA0577013939'
        }
    };

var req = https.request(options, function (res) {
    console.log("result : ",result);
});
copy
public class TwikeyAPI {
    private String host = "https://api.twikey.com",
        authorization = null; //collected through logIn
    public void ibanNameCheck(){
        OkHttpClient client = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
            .add("name", "Doortje Doorzon")
            .add("iban", "NL51ABNA0577013939")
            .build();

        Request request = new Request.Builder()
            .url(host + "/creditor/ibancheck")
            .post(body)
            .addHeader("content-type", "application/x-www-form-urlencoded")
            .addHeader("authorization", authorization)
            .build();

        Response response = client.newCall(request).execute();
    };
}
copy
public class TwikeyAPI {
    private String host ="https://api.twikey.com",
        authorization =null; //collected through logIn

    public void ibanNameCheck(){
        RestClient client = new RestClient(host + "/creditor/ibancheck");
        RestRequest request = new RestRequest(Method.POST);
        request.AddHeader("cache-control", "no-cache");
        request.AddHeader("content-type", "application/x-www-form-urlencoded");
        request.AddHeader("Authorization", authorization);
        request.AddParameter("application/x-www-form-urlencoded",
            "name=Doortje Doorzon" +
            "iban=NL51ABNA0577013939"
            , ParameterType.RequestBody
        );
        IRestResponse response = client.Execute(request);
    }
}
Responsecopy
{
    "iban": {
        "found": true,
        "valid": true,
        "blackListed": false,
        "nameMatching": true
    },
    "name": {
        "valid": true,
        "suggestion": "some suggestion"
    },
    "account": {
        "foreign": false,
        "countryCode": "NL",
        "active": true,
        "holderType": "NP",
        "holderResidenceMunicipality": "Amsterdam",
        "holderOfficialName": "",
        "holdersNumber": 1,
        "holderJointAccount": false
    }
}

IBAN Name Check Netherlands

Request Parameters

NameDescriptionRequiredTypeMax.
nameName of the account holderYesstring70
ibanAccount numberYesstring34
flavourit or nl integration to use, by default NL is usedNostring2

Response Parameters

NameDescriptionType
iban.foundTrue if the IBAN is found in the registryboolean
iban.validTrue if the specified IBAN has a valid formatboolean
iban.blackListedTrue if the iban is blacklistedboolean
iban.nameMatchingTrue if the name matches the name of the account holderboolean
name.validThe name registered in the system (not the one provided) is valid [*1].boolean
name.suggestionWhen the provided name doesn't match the account holders name or type [*2].string
account.foreignTrue if the account is Dutchboolean
account.countryCodeCountry code of the account (ISO 2 characters)string
account.activeTrue if the account is activeboolean
account.holderTypeType of account holder. [*3]string
account.holderResidenceMunicipalityCity of the account holderstring
account.holderOfficialNameOfficial name of the account holderstring
account.holdersNumberNumber of account holders for this accountnumber
account.holderJointAccountTrue if it is a joint accountboolean

[*1] The name registered in the system (not the one provided) is valid

[*2] When the provided name doesn't match the account holders name or type

[*3] Type of account holder.

IBAN Name Check Italy

This checks that the IBAN and the VAT number or fiscal code match.

Request Parameters

Both codfisand vatno are considered optional but at least one value should be passed when making the request.
When both values are given and both values are not empty strings vatno will take precedence over codfis.

NameDescriptionRequiredTypeMax.
ibanAccount numberYesstring34
flavourit or nl integration to use, by default NL is usedYesstring2
codfisFiscal code related to the IBANNoString
vatnoVAT Number related to the IBANNoString

Response Parameters

NameDescriptionType
unsupportedcheck is not supported for specified IBANboolean
validIBAN check result is validboolean
blacklistedthe given IBAN is blacklisted in our systemboolean

HTTP Response

CodeDescription
200The request has succeeded
400User error if parameter is given but not valid (available in apierror header and response)

Error codes

CodeDescription
err_no_integrationNo integration was configured
err_invalid_nameThe provided name is invalid
err_err_invalid_ibanThe provided account is invalid
err_fail_integrationAn error occurred on the system
err_missing_attributesmissing required parameters

Accepted SEPA Characters

Here you can find a list of the accepted SEPA characters that are accepted for transactional endpoints.

Responsecopy
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
/ - ? : ( ) . , ' +
Space

Reset the feed

When you need to access previous data because your internal system is out of sync for example, you can reset it.
The reset can only be done using the interface. To do this:

A modal will open where you can select a specific feed (Contracts, Transactions, Payment links, ..).
By default 'Completely reset feed' is enabled. This applies only to the selected feed

When you disable this option you can enter a custom date and time to which that feed needs to be reset.
Once done click on 'Apply' and the feed is reset.

Webhooks

In order to reduce the number of polling requests, one can opt to implement a webhook endpoint and use this as a way to trigger polling requests. The endpoint is set in the API section of the settings screen and will convey information about various events in Twikey. The webhook should not be used to replace the API requests as the API delivers a lot more detailed information and we continue to improve the information available in the api calls. The call is done via a simple GET with basic (non-sensitive) parameters eg. http://my.company.com/callback?type=contract&mandateNumber=MNDT123&state=signed...

copy
use Twikey\Api;

$apiKey = "**API_KEY**";

// Get the message differently depending on the request method
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    // Get the provided signature from headers
    $provided_signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

    $message = urldecode($_SERVER['QUERY_STRING']); // Query string for GET requests
    
    // Securely compare the signatures
    $valid = Twikey::validateWebhook($apiKey, $message, $provided_signature)
    if ($valid) {
        echo "Signature is valid.";
    } else {
        die("Invalid signature.");
    }
} else {
    die("Unsupported request method.");
}

A header X-Signature is included when a webhook is sent to your server, this is to ensure that the request is indeed from Twikey.

To verify the X-Signature for a GET request, the query string part should be compared with the X-Signature header using the HMAC256 algorithm. Note that the payload should be URL-decoded before computing the HMAC-SHA256 hash to ensure it's correctly processed.

The SDK computes the hash of the url decoded payload using your apiToken (API Key) and compare it in a secure way to the X-Signature header from the request to ensure they match.

Url decoding is used to URL-encode characters back to their original form. For example, spaces are encoded as %20, and special characters like &, =, or others might be represented with percent signs followed by their hexadecimal values. For instance, if the payload is: param1=value1&param2=value%202&param3=value%26 URL-decoding it would change %20 to a space and %26 to &, resulting in: param1=value1&param2=value 2&param3=value&

Setup the different webhooks in your Twikey Dashboard under 'Settings: Api'. The scope selection determines the webhooks you'll receive (documents, payments, customer updates and other events).

You can test your implementation by using the 'Test' button in your environment (Api settings) which will send the sample payload msg=dummytest&type=event as text.

Asynchronous Processing

When receiving a webhook some may opt to perform some methods first and only return a response to us once the methods are completed. This approach is highly discouraged, especially since it is possible that the response won't be returned and Twikey will consider the receiver as non-responsive. If this was the case Twikey will re-offer the same webhook again which will trigger your logic again.

To ensure a good flow, asynchronous processing is therefor (strongly) recommended.

Retry mechanism

When a HTTP request is made, but we receive an error code (e.g. 3xx, 4xx, 5xx) the request is marked to be retried. If a status is non-2xx we consider it a temporary issue and will retry as soon as time permits (usually within a couple of seconds). This will however not block other webhooks.

If however we get a timeout or no response at all we consider there is something wrong on your end and we'll block temporarily all webhooks. We'll retry however after 30sec./5min./30min to stop an hour. Once your side becomes responsive again you can retry the failed messages by pushing the retry button in the app. Hence the importance to not try to do your 'real' work during the webhook.

Note that the url to be given in the interface is without parameters since these are filled up depending on the type.

Types

type=contract

copy
curl -H "apiToken=MyToken" https://example.com/callback \
    -d type=contract \
    -d reason=resumed \
    -d mandateNumber=CORE01 \
    -d name=<empty> \
    -d contractNumber=General terms and conditions \
    -d state=SIGNED \
    -d event=Update

Triggered when something happens to the mandate. Activation events will be included in the event=Update with the reason being either 'resumed' or 'suspended'. Update events can also contain a specific _Txx code.

Specific codes:

type=plan

type=customer

Triggered on customer updates.

Eg. When two customers are merged, you receive a webhook for both customers. If a customer has documents, you'll recieve a webhook with type=contract for every document.

type=payment

copy
type=payment

type	payment
copy
type=payment (one-off payment)
amount 45.42
ct      2223
id      186229
ref     123456
status  paid
type    payment

Triggered when a payment was updated. Webhooks involving payments can have multiple origins. Either it's a one-off payment via a psp in which case this webhook will provide you with the details. In case a whole batch of payments is received (eg. Sdd) then we only send a webhook once and depending on the type of transaction (be it invoice or sdd) you can call the respective feed.

type=dunning

type=letter

copy
type             letter
mailId           JJBEA120090000185103100
docType          contract
createdOn        2021-11-17 10:58:01.962
sentOn           2021-11-17
delivered        true
docNumber        WIKLETTER420

Triggered when a letter was delivered or bounced.

Extra parameters:

Thank you page

An alternative to a webhook can be the (Global) Thank you page or the page where the end-user is being redirected to after signing. This is an URL that can be defined on each profile and for different languages.
This url can contain variables that are filled in depending on the outcome. The variables can either be positional or named.

Some of the exceptional statusses can be (but not limited to):

(Global) Thank you pages can be defined for mandates and invoices as described in How to define a Thank you page.

In order to be sure that the return call is coming from Twikey, it's strongly recommended to verify the signature to the Thank you page. View our sample code snippets to find examples in various languages on how to calculate the signature.

eg. a Thank-you page with the following value

http:///website.com/{0}/{1}/{3}

or

http:///website.com/{{mandateNumber}}/{{status}}/{{s}}

would be expanded to if the end-customer returned without error

http:///website.com/MND123/ok/C9FB0D93...

Test data

In our Beta environment you can simulate specific outcomes.

Transactions outcomes

Every 30 minutes, a job is run to mock feedback from the bank. Transactions sent to the bank then receive a paid or failure state with a specific error code depending on their amount. You can test your failed payments configuration (dunning) using these amounts.

Important: To mimic the same behaviour as in a live environment, the first feedback is always PAID.
The second time the job runs, the actual feedback is returned.

Dunning

The grace period in days that you define in your Dunning Steps, are reduced to minutes in BETA to avoid that you need to wait days to verify if your dunning is configured and executed as expected.

Depending on the number of days that you defined, the next dunning step is triggered (1 day = 1 minute).
The shorter the grace period, the quicker that the next step is executed.

B2B Mandate acceptance

Every 30 minutes, a job runs that accepts/rejects a mandate based on the last number of the iban. If the number is even, the B2B mandata will be accepted, otherwise will be rejected. Note: this is not the case for Belfius (BIC = GKCCBEBB), BNP (BIC=GEBABEBB) account numbers.

Please use the below examples for automated feedback testing purposes:
B2B Mandate accepted scenario --> BE16 6453 4897 1174
B2B Mandate rejected scenario --> BE17 3217 8221 4921

Only possible using connected banks
For Dutch B2B signing you can simulate the desired outcome (see below under 'iDeal, Emachtiging and iDIN'). For not-connected banks only the print and mark as signed flows are possible

B2B Automated validation

In BETA the outcome of the transaction to validate the mandate is done based on the IBAN used on the mandate.
IBAN's ending on an even number are accepted after 4 hours to simulate the waiting time in production (circa 5 bank days) before the mandate is set on signed after a successful validation. IBAN's ending with an uneven number are rejected, this directly when the transaction received final bank feedback (can take up to 1 hour).

in both cases, the first bank feedback is always PAID, only after the second feedback of your batch the actual feedback is returned and the mandate accepted or rejected.

Test accounts

Success: DE43100000000123456780
Failure: DE60740201008441962539
Failure / Success: DE80783500000040982001

These are just example accounts. You can use any not-connected account (that is B2B enabled), the last number defines the behaviour:

Simulating feedback

In beta a jobs runs about every 30 minutes to simulate feedback of the bank. This needs to run twice to get the final outcome (as the bank also always returns feedback twice in a live environment).
When you test a successful validation: a task is created to run after 4 hours. Once that task runs the mandate is accepted. When you opt to use an IBAN with '00' at the end, the mandate is signed directly after the second job ran.

Possibly you will receive a duplicated webhook for the same mandate due to the task that runs. this is a know issue occurring only in BETA.

Sign methods and data

iDeal, Emachtiging and iDIN

Contact your customer success agent to link your template to a test bank. Provide a template ID and signing method.

When using iDIN to sign or for identification, the customer data is overwritten by default to simulate as if you received the data back from iDIN as it would in a live environment. This can be disabled or customized by your customer success agent.

You can also add a delay for these signing methods. Those will delay the feedback in the back. On the frontend you will directly go to the verification page and not see this. The transaction is then verified on a periodic base.

Other signing methods

Letters

When sending invites (documents, invoices, ..) note that no real letters are sent out. A copy of the letter is sent to you (the user performing the action) via email. This way you can verify the content before going live.

WIK letters need to be released by your Customer Success Agent. You then receive a zip file (to the email address configured under Settings -> Company information) containing the PDF's files.

Wire transfer

You can simulate a wire transfer. This enables you to test automatic and manual matching. You need to replace the IBAN with an actual account from one of your bank gateways.
Create a CSV file with following contents:

copy
twikey:BE123456789123
name;iban;bic;msg;amount
John Doe;BE31798258915655;GKCCBEBB;My product;100

Note: the amount is in cent

Upload this file under Reconciliation - 'Upload account info'. Note: the filename must be unique for each upload.

Automatic matching of an invoice:

When matched, the invoice is marked as paid.

Webhooks

You can also test our webhooks to ensure they are correctly handled on your end and that the signature is correctly calculated. When activating the webhook via the API settings, no parameters are required for the url. Those are added when we send out the request and can be different depending on the type.

For the full list of types and details you can refer to the Webhook documentation.

Collection Agency (Intrum Only)

After a transaction was sent to the collection agency feedback is received every 15 minutes (or until the case is closed).
When using the Intrum-API integration you can use the following amounts for your transactions to simulate feedback from the agency:

copy
case 1, 2, 3, 4, 5 -> Paid in full
case 6, 7, 8, 9, 10 -> Paid in full with 1 euro incremental payments
case 11 -> Closed not paid with exit BANKRUPTCY
case 12 -> Closed not paid with exit DELETED
case 13 -> Closed not paid with exit LACKOFEVIDENCE
case 14 -> Closed not paid with exit DECEASED
case 15 -> Closed not paid with exit DEBTRELIEF
case 16 -> Closed not paid with exit DISPUTED
case 17 -> Closed not paid with exit COMBINEDCASE
case 18 -> Closed not paid with exit NOTCOLLECTABLE
case 19 -> Closed not paid with exit FRAUD
case 20 -> Closed not paid with exit NOREACTION
anything else -> Partial payment

When using the header X-Purpose in test and you create a QR code from it, this code can not be scanned by bank apps.
Only using our production environment you can create a valid url.

The url itself can still be used in our BETA environment, only when generating a QR from it, this is not valid for bank apps.

Changes

Upcoming Changes

Production Changes