# Twikey API reference
> Complete API reference for the Twikey payment platform. Source: https://www.twikey.com/api/
## Introduction
```bash
By default, the Twikey API Docs demonstrate API interaction using cUrl.
You can always select a specific language from the selection above.
```
```python
# The easiest way to install the Twikey API client is with pip.
pip install twikey-api-python
```
```php
// By far the easiest way to install the Twikey API client is to require it with Composer.
$ composer require twikey/twikey-api-php:^0.4.0
{
"require": {
"twikey/twikey-api-php": "^0.4.0"
}
}
```
```javascript
// Using npm
$ npm install twikey-api-client
// or using yarn
$ yarn add twikey-api-client
```
```java
// The easiest way to install the Twikey API client is with maven.
## 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](#webhooks).
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](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/)
idempotency header for our creation endpoints of transactions, refunds, subscriptions, payment links and collections.
`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 same convenience 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](https://github.com/twikey/development-kit) 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.__
```bash
curl -X POST https://api.twikey.com/creditor \
-d apiToken=**API KEY**
```
```php
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use Twikey\Api;
$httpClient = new Client();
$APIKEY = "**API_KEY**";
$twikeyclient = new Twikey($httpClient,$APIKEY);
```
```javascript
let twikeyClient:TwikeyClient = new TwikeyClient({
apiKey: "apiKey",
apiUrl: "https://mycompany.twikey.com/api/creditor",
userAgent: "myApp",
});
```
```java
public class TwikeyAPI {
private String apiKey = "**API_KEY**";
public void logIn(){
TwikeyClient twikeyClient = new TwikeyClient(apiKey)
.withUserAgent("myApp");
}
}
```
```cs
public class TwikeyAPI {
private String apiKey ="**API_KEY**";
public void logIn(){
TwikeyClient twikeyClient = new TwikeyClient(apiKey)
.WithUserAgent("myApp");
}
}
```
```go
import "github.com/twikey/twikey-api-go"
func main() {
client := twikey.NewClient("**API_KEY**")
}
```
```python
import twikey
APIKEY = '**API_KEY**'
twikeyClient = twikey.TwikeyClient(APIKEY, "https://apiurl_as_found_in_twikey")
```
**Response**
```json
{
"Authorization": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```
> This session token has a validity of 24h. The response also includes an `X-MERCHANT-ID` header with your numeric company id.
### HTTP Request
`POST https://api.twikey.com/creditor`
### Form Parameters
| Name | Description | Required | Type |
|----------|-----------------------------------------------------------------------|----------|--------|
| apiToken | API key | Yes | string |
| otp | Value calculated based on salt and private key (if enhanced security) | No | long |
### HTTP Response
| Code | Description |
|------|-------------------------------|
| 200 | Always, 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
| Code | Description |
|--------------------|-----------------------------------------------|
| err_no_login | Not logged in |
| err_not_authorised | Too many failed login attempts (rate limited) |
## Logout
Invalidates the AuthorizationToken by making a GET request to /creditor
```bash
curl https://api.twikey.com/creditor \
-H 'authorization: authorization'
```
### HTTP Request
`GET https://api.twikey.com/creditor`
### HTTP Response
| Code | Description |
|------|-------------------------|
| 204 | Logged 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](#exit-url)
> Requires a **ct_id** which can be found in the [Twikey Creditor Template overview](/r/admin#/c/settings/templates)
```bash
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'
```
```php
$host = "https://api.twikey.com";
$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=123"
."&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);
```
```javascript
let invite = await twikeyClient.document.create({
ct: Number(CT),
email: "no-reply@example.com",
firstname: "Twikey",
lastname: "Support",
address: "Stationstraat 43",
city: "Gent",
zip: "9000",
country: "BE",
l: 'nl',
})
console.log("Redirect to : " + invite.url)
```
```java
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();
}
}
```
```python
import twikey
ct = 1
invite = twikey.document.create(
InviteRequest(
ct=ct,
email="no-reply@twikey.com",
first_name="Info",
last_name="Twikey",
l="en",
address="Abby road",
city="Liverpool",
zip="1526",
country="BE",
mobile="",
iban="",
bic="",
)
)
```
```cs
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);
}
}
```
**Response**
```json
{
"mndtId": "COREREC01",
"url": "http://twikey.to/myComp/ToYG",
"key": "ToYG"
}
```
### HTTP Request
`POST https://api.twikey.com/creditor/invite`
### Query Parameters
| Name | Description | Required | Type | Max. |
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------------|------|
| ct | Profile id to use | Yes | integer | |
| l | Language (en / fr / fr_fr / nl / nl_nl / de / pt / es / it) | No | string | 2 |
| iban | International Bank Account Number of the debtor | No | string | 35 |
| bic | Bank Identifier Code of the IBAN | No [***6**] | string | 11 |
| mandateNumber | Mandate Identification number (if not generated) [*1] | No | string | 34 |
| customerNumber | The customer number (strongly advised) | No | string | 50 |
| email | Email of the debtor | No | string | 70 |
| lastname | Lastname of the debtor | No | string | 50 |
| firstname | Firstname of the debtor | No | string | 50 |
| mobile | Mobile number required for sms (International format +32499123445) | No | string | 50 |
| | To update the address all address fields parameters are required | Yes | - | - |
| address | Address (street + number) | No | string | 70 |
| city | City of debtor | No | string | 50 |
| zip | Zipcode of debtor | No | string | 12 |
| country | ISO format | No | string | 2 |
| companyName | The company name. **Required for B2B profiles**. If omitted for B2B profiles, it defaults to "Unknown". | Conditional | string | 140 |
| coc | The enterprise number (if debtor is company) | | | |
| vatno | VAT number of the company | No | string | 50 |
| contractNumber | The contract number which can override the one defined in the template. | No | string | 35 |
| campaign | Campaign to include this url in | No | string | 250 |
| prefix | Optional prefix to use in the url (default companyname) | No | string | 20 |
| check | If a mandate already exists, don't prepare a new one (based on email, customerNumber or mandatenumber and + template type(=ct)) | No | boolean | |
| ed | Expiry of the link (max.12 months in the future). **Epoch timestamp in milliseconds** | No | number | |
| reminderDays | Send a reminder if contract was not signed after number of days. This parameter is ignored if you have automatic reminders enabled on the profile | No | number | |
| sendInvite | Send out invite directly [*2] | No | boolean/string | |
| token | (optional) token to be returned in the exit-url (lenght<100) **Percent Encoded characters are not supported** | No | string | |
| requireValidation | Always start with the registration page, even with all known mandate details | No | boolean | |
| document | Add a contract in base64 format | No | string | |
| transactionAmount | In euro for a transaction via a first payment or post signature via an SDD transaction | No | string | |
| transactionMessage | Message for the transaction [*3] | No | string | |
| transactionRef | Reference of the transaction [*4] | No | string | |
| plan | Name of the the plan. see special cases **Add a plan on invite** below for more information. | No | string | |
| subscriptionStart | Start date of the subscription (yyyy-mm-dd). This is also the first execution date. Required if adding a subscription | No | date | |
| subscriptionRecurrence | 1w / 1m / 2m / 3m / 6m /12m (default 1m) | No | string | |
| subscriptionStopAfter | Number of times to execute the subscription | No | number | |
| subscriptionAmount | Amount for every transaction | No | number | |
| subscriptionMessage | Message to the subscriber | No | string | |
| subscriptionRef | **Unique** Reference of the subscription (important for updates) [*5] | No | string | |
[*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:
* **true**: Invite is sent by email (requires an active email integration).
* **letter**: Invite is sent by letter
* **letterWithMandate**: Invite is sent by letter incl. the mandate
[*3] The transaction message is either:
* The payment link title (displayed on screen when the customer is on the payment page) when the first payment is via a payment link
* The transaction communication when the first payment is via a transaction
[*4] The transaction reference is either:
* The payment link remittance when the first payment is via a payment link
* The transaction reference when the first payment is via a transaction
[*5]
* The reference is a unique identifier for each subscription. This allows the subscription to be referred to when using a request.
[*6]
* The BIC code can be derived in most cases and is not mandatory. In some cases (foreign IBAN's) we can not derive the BIC code
and return the error `err_invalid_bic`. In this case pass the BIC code in your request.
### Special cases:
* __Recurring Credit Card__
For this you have to include an amount, the 'ct' should contain the number of a template of type credit card and optional a parameter 'method' (mastercard/visa) in the request.
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.
* __Recurring Credit Card - Paypal via CCV__
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 `method`with value `paypal`
* __Add a plan on invite__
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:
* Put a default plan on a profile. Go to the **profile > Payments > Default plan** and select a plan. Note: This will always add a subscription when an agreement is signed.
* Or, if you do not have a default plan configured, add an attribute **named 'plan'** and of **type 'plan'** in the profile.
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
| Code | Description |
| ---- | ----------- |
| 200 | If the check option is provided and an existing collectable mandate is available it will be returned. Otherwise an url and key will be returned. |
| 400 | User error if parameter is given but not valid or collectable (available in apierror header and response) |
### Error codes
| Code | Description |
|----------------------------|----------------------------------------------------------------------------------------------|
| err_no_such_ct | No template found (or not active) |
| err_mandatenumber_required | No mandatenumber was given, while setting does not allow generation |
| err_double_mandatenumber | Signed mandate with the same mandatenumber already exists |
| err_missing_params | Attributes are missing while configured as mandatory |
| err_contract_signed | Specified contract is already signed |
| err_invalid_mandatenumber | Reference or prefix is invalid |
| err_bic_not_sepa | BIC code does not belong to a SEPA bank |
| err_duplicate_ref | Duplicated reference |
| err_invalid_bic | BIC code invalid |
| err_invalid_coc | Invalid enterprise number |
| err_invalid_country | Country code invalid |
| err_invalid_email | Email invalid |
| err_invalid_iban | IBAN invalid |
| err_invalid_lang | Language code invalid |
| err_too_long | The value of a parameter is longer then allowed |
| err_email_disabled | No email integration is configured or all emails are disabled. Unable to send the invitation |
## Sign a mandate
```bash
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=support%40twikey.com' \
-d '&lastname=Twikey' \
-d '&firstname=Support' \
-d '&mobile=%2B32479123123' \
-d '&address=Stationstraat%2043' \
-d '&city=Gent' \
-d '&zip=9051' \
-d '&country=BE'
```
```php
$host = "https://api.twikey.com";
$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=support%40twikey.com"
."&lastname=Twikey"
."&firstname=Support"
."&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);
```
```javascript
let invite = await twikeyClient.document.sign({
method: 'itsme', // or emachtiging / idin / ....
ct: Number(CT),
email: "support@twikey.com",
firstname: "Twikey",
lastname: "Support",
address: "Stationstraat 43",
city: "Gent",
zip: "9000",
country: "BE",
l: 'nl',
})
```
```java
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();
}
}
```
```python
import twikey
ct = 1
signed_mandate = twikey.document.sign(
SignRequest(
ct=ct,
l="en",
iban="NL46ABNA89119718",
bic="GKCCBEBB",
customer_number="CUST001",
email="joe.doe@gmail.com",
last_name="Doe",
first_name="John",
address="Main Street 1",
city="Brussels",
zip="1000",
country="BE",
ed="2025-07-31",
transaction_ref="TXN001",
method=SignMethod.ITSME,
place="Brussels",
)
)
```
```cs
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=support%40twikey.com" +
"&lastname=Twikey" +
"&firstname=Support" +
"&mobile=%2B32479123123" +
"&address=Stationstraat%2043" +
"&city=Gent" +
"&zip=9051" +
"&country=BE" +
, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
}
}
```
**Response**
```json
{
"MndtId": "MyMandateId"
}
```
**Response with sign method Itsme**
```json
{
"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.
### Signing methods
| Method | CORE / Contract | B2B |
|-----------------------|:---------------:|:---------:|
| `bancontact` / `bcmc` | ✅ | ❌ |
| `ideal` | ✅ | ❌ |
| `sms` | ✅ | ❌ |
| `itsme` | ✅ | ❌ |
| `paper` | ✅ | ✅ |
| `import` | ✅ | ✅ |
| `digisign` | ✅ | ❌ |
| `emachtiging` | ✅ | ✅ NL only |
| `idin` | ✅ | ❌ |
| `ais` | ✅ | ❌ |
| `auto` | ✅ | ❌ |
---
- **bancontact** (or _bcmc_): not supported for B2B. Use the invite a mandate request instead.
- **ideal**: not supported for B2B. Use the invite a mandate request instead.
- **sms**: requires the `mobile` parameter. Not supported for B2B. Use the invite a mandate request instead.
- **itsme**: not supported for B2B via the sign API. Use the invite a mandate request instead.
- **paper**: preview the document or send a print invite via email.
- 'Print the document and return the signed document via e-mail or upload' must be enabled in the template under **Options**.
- Include the parameter `sendInvite: true` to directly send the invitation via email.
- When `sendInvite` is not passed or `false`, nothing is sent out.
- The response includes a preview of the PDF in base64 format.
- **import**: import the mandate with a specific sign date.
- Either reference an existing customer using `email` or `customerNumber`.
- For new customers, pass the address parameters (`address`, `city`, `zip`, `country`).
- **B2B**: when using `import` for a B2B profile, the mandate is directly **signed**. To require bank validation, use `bankSignature: false`.
- **digisign**: wet signature encoded as a base64 PNG (max 150 KB). Not supported for B2B (wet signature is not an allowed B2B sign method).
- **emachtiging**, **idin**: require the `bic` parameter.
- **iDIN is not supported for B2B mandates.** Use `emachtiging` for Dutch B2B.
- **You can retrieve the connected banks and BIC codes using [fetch connected banks](#fetch-connected-banks)**
- **ais**: Account Information Services — the person signs the agreement via their own bank.
- **auto**: uses the default template and selects the best available signing method automatically (AIS → eMachtiging → iDeal FP → Bancontact Pay FP → SMS). auto on a B2B profile only succeeds if Emachtiging is the top-priority enabled mechanism on the template AND AIS is not enabled. Optionally combine with `ct` to use a specific template.
**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](#invite-a-customer) +
| Name | Description | Required | Type | Max. |
|---------------|-------------------------------------------------------------------------------------------------------------------|----------|---------|------|
| method | Method to sign (sms/digisign/import/itsme/emachtiging/paper,...) | Yes | string | |
| digsig | Wet signature (PNG image encoded as base64) required if method is digisign | No | string | |
| key | shortcode from the invite url. Use this parameter instead of 'mandateNumber' to directly sign a prepared mandate. | No | string | 36 |
| bic | Required for methods _emachtiging_ and _iDIN_ | No | string | 11 |
| signDate | Date of signature (iso8601, eg. 2025-12-31 ), sms uses date of reply | No | string | |
| place | Place of signature | No | string | |
| bankSignature | For **B2B** only, require bank validation if set on `false`. The value is `true` by default. | No | boolean | |
### HTTP Response
| Code | Description |
| ---- |--------------------------------------------------------------------------------------------|
| 200 | The request has succeeded |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|----------------------------|---------------------------------------------------------------------|
| err_no_such_ct | No template found (or not active) |
| err_mandatenumber_required | No mandatenumber was given, while setting does not allow generation |
| err_double_mandatenumber | Signed mandate with the same mandatenumber already exists |
| err_missing_params | Attributes are missing while configured as mandatory |
| err_invalid_signature | No valid method was provided |
| err_bic_not_sepa | BIC code is not SEPA |
| err_contract_signed | Contract is already signed |
| err_invalid_state | Invalid state or has unpaid transactions |
| err_not_authorised | sign method not authorized |
| err_invalid_bic | BIC code is invalid |
| err_invalid_email | Email is invalid |
| err_invalid_iban | IBAN is invalid |
| err_invalid_mobile | Mobile number format is invalid |
| smsErrorSending | Error sending SMS |
| smsFixed | Phone number is fixed instead of mobile |
| smsPendingContract | Mobile number currently in use and awaiting response from customer |
| err_contact_support | Configuration problem, contact support |
## Mandate actions
```bash
curl -X POST "https://api.twikey.com/creditor/mandate/MANDATE_ID_HERE/action" \
-H "Authorization: Bearer YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "type=invite"
```
```python
import twikey
twikey.document.action(
MandateActionRequest(
mandate_number="MNDTNUMBER",
type="reminder",
reminder="1"
)
)
```
This endpoint allows you to trigger specific actions on a mandate. The following action types are supported:
| Type | Description |
|------------------|--------------------------------------------------------------------|
| `invite` | Sends an invitation email to the customer. |
| `reminder` | Sends a reminder email to the customer. |
| `access` | Grants the customer access to their mandate. |
| `automaticCheck` | Enables automatic validation for B2B mandates. |
| `manualCheck` | Disables automatic validation for B2B mandates. |
### HTTP Request
`POST https://api.twikey.com/creditor/mandate/{mndtId}/action`
### Query Parameters
| Name | Description | Required | Type | Max Length |
|------------|---------------------------------------------------------------------------------------------------|----------|--------|------------|
| `type` | The action type to execute (`invite`, `reminder`, `access`, `automaticCheck`, `manualCheck`). | Yes | string | – |
| `reminder` | Specifies which reminder to send (valid values: 1 to 4). Used only when `type=reminder`. | No | string | – |
### HTTP Response
| Code | Description |
|------|-----------------------------------------------------------------------------|
| 204 | The request has succeeded |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error Codes
| Code | Description |
|-------------------|-------------------------------------------------|
| `err_invalid_type`| The provided action type is invalid. |
| `err_invalid_state`| Failed to update mandate state. Verify status. |
## Mandate feed
```bash
curl https://api.twikey.com/creditor/mandate \
-H 'authorization: **authorization**' \
```
```php
$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");
}
}
);
```
```python
import twikey
class MyDocumentFeed(twikey.DocumentFeed):
def new_document(self, doc: twikey.Document, evt_time):
print("Document created ", doc.mandate_number, "@", evt_time)
def updated_document(self, original_doc_number: str, doc: twikey.Document, reason: str, author: str, evt_time):
print("Document updated ", original_doc_number, "b/c", reason, "@", evt_time)
def cancelled_document(self, doc_number: str, reason: str, author: str, evt_time):
print("Document cancelled ", doc_number, "b/c", reason, "@", evt_time)
twikey.document.feed(MyDocumentFeed())
```
```cs
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));
}
}
```
```go
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)
})
```
```java
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);
}
})
```
```javascript
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)
}
}
```
**Response**
```json
{
"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": "Bargelaan 200",
"PstCd": "2333CW",
"TwnNm": "Leiden",
"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:
* Updating a CRM system to take appropriate actions (eg. call the customer on cancel..)
* Integrate the info in an ERP system to create the correct SDD files (in case of mandates)
In order too avoid polling, a [webhooks](#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.
* The first one is when a new signed mandate is available.
* The second is an amendment on a mandate (like address change). Indicated by the existence of an "AmdmntRsn".
* Last one is a cancellation of a mandate. Indicated by the existence of a "CxlRsn"
**Amendments and cancellation**:
* Updates on unsigned (prepared) agreemenets are not included
* Retrieving the feed after a mandate was signed and cancelled will only return the event for the cancel.
### 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](https://github.com/twikey/development-kit/blob/master/docs/message.md) 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, .. .
* The signer(s) name: Signer#0, Signer#1, ..
* The method used by each signer: SignerMethod#0
* The place: SignerPlace#0 (city name, imported, ..)
* The date: SignerDate#0
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:
* **AmdmntRsn**
* **Orgtr** (contains the details about the initiator)
* **Rsn**
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:
* Rsn: uncollectable|user (mandate was suspended by a user)
* Rsn: collectable (mandate was activated, collectable again)
* Rsn: _T50 (specific T5 codes - see [Types: Specific codes](#types))
* Rsn: key|value (a (custom) attribute value was changed or added)
* This can be a 'plan' for example or a custom attribute 'amount'
* E.g.: "change|amount=5000" (attribute "amount" was changed to a new value)
* Rsn: _DUN (mandate is cancelled due to a dunning step)
#### Cancelled
When a mandate was cancelled, this is returned as Cancelled reason with initiator and reason:
* **CxlRsn**
* **Orgtr** (contains the details about the initiator)
* **Rsn**
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:
* Rsn: Transaction disputed (automated dunning step to cancel the mandate in this case)
* Rsn: custom reason (by user, customer,..)
### HTTP Request
`GET https://api.twikey.com/creditor/mandate`
### Headers
| Header name | Description |
|----------------|-----------------------------------------------------------------------------------------------------|
| X-RESUME-AFTER | Resume the feed after a specific sequence id |
| X-TYPES | Types of contracts to include (default = CORE,B2B,CREDITCARD). Main exception is CONTRACT and IDENT |
### 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`
| Name | Returned parameters |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| include=mandate | TemplateId, MandateName, custom attributes |
| include=person | FirstName, LastName, Language |
| include=signature | SignerMethod#0, Signer#0, SignerPlace#0, SignerDate#0 |
| include=plan | Plan |
| include=tracker | TRACKER (shortcode used for invite) |
| include=cancelled_mandate | returns additional customer information on a cancel feed (email and customerNumber) |
| include=paidamount | return the amount paid when signing the mandate (returned in the `splmtryData ` object) |
| inlude=payment | return either the payment link id or transaction id in the `splmtryData` object when the mandate is signed via first payment using a PSP. The payment message is also returned. |
### HTTP Response
| Code | Description |
|-------|-----------------------------|
| 200 | The request has succeeded |
### Error Codes
| Code | Description |
|----------------------|-----------------------------------------------|
| err_call_in_progress | Request 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.
```bash
curl https://api.twikey.com/creditor/mandate/query?iban=BE21798857497403 \
-H 'authorization: **authorization**' \
```
```php
$host = "https://api.twikey.com";
$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);
```
```javascript
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);
});
```
```java
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();
}
}
```
```python
import twikey
result_set = twikey.document.query(
QueryMandateRequest(
iban="BE51561419613262",
customer_number="customer123",
email="no-reply@twikey.com",
)
)
```
```cs
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);
}
}
```
**Response**
```json
{
"Contracts": [
{
"id": 3291639,
"type": "CORE",
"state": "SIGNED",
"suspended": false,
"pdfAvailable": true,
"mandateNumber": "MNDT123",
"contractNumber": "General Terms",
"ct": 2223,
"signDate": "2024-03-01",
"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-01",
"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 `email`is required. `state` is optional. A combination of these parameters is possible.
| Name | Description | Required | Type | Max. |
|----------------|-----------------------------------------------------------------------------------------------------------------------------------|----------|--------|------|
| iban | The IBAN of the contract | Yes | string | 35 |
| customerNumber | The customer number | Yes | string | 50 |
| email | The email address of the customer | Yes | string | 70 |
| state | optional, return only contracts in a specific state (SIGNED by default. Value in **uppercase**) | No | string | |
| page | Pagination is returned at the end of the response, include 'page' with a page number to go to the next or previous set of results | No | number | |
* **customerNumber** is prioritized over email when you combine both.
* **state**: possible values are `SIGNED`, `PREPARED` or `CANCELLED`.
* **email**: special characters like the plus (+) sign are not supported. Others like dot, hyphen, underscore are supported.
### HTTP Response
| Code | Description |
|------|--------------------------------------------------------------------------------------------|
| 200 | The request has succeeded |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
| 429 | Too many requests |
### Error codes
| Code | Description |
|--------------------|---------------------------------------------------------|
| err_not_found | No contracts found with the given parameter(s) value |
| err_missing_params | Missing 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.
```bash
curl -X DELETE https://api.twikey.com/creditor/mandate?mndtId123&rsn=test \
-H 'authorization: **authorization**'
```
```php
$host = "https://api.twikey.com";
$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);
```
```javascript
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);
});
```
```java
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();
}
}
```
```python
import twikey
mandate_number = "MNDT123"
twikey.document.cancel(mandate_number, "reason for cancel")
```
```cs
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
| Name | Description | Required | Type | Max. |
|--------|-------------------------------------------|----------|---------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| rsn | Reason of cancellation (Can be R-Message) | Yes | string | 200 |
| notify | Notify the customer by email when true | No | boolean | |
### HTTP Response
| Code | Description |
|------|--------------------------------------------------------------------------------------------|
| 200 | The request has succeeded |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|------------------------------|---------------------------------------------------------------------------------------------------|
| err_no_contract | No mndtId was provided or invalid mndtId supplied |
| err_provide_reason | Please provide reason |
| err_invalid_state | Contract state does not allow a cancel (eg. already cancelled) |
| err_invalid_cancel_forbidden | Due 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.
```bash
curl https://api.twikey.com/creditor/mandate/detail?mndtId=mndtId123 \
-H 'authorization: **authorization**'
```
```php
$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);
```
```javascript
const details = await client.document.detail(importedMandate);
console.log(details);
```
```java
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();
}
}
```
```python
import twikey
mandate_number = "MNDT123"
fetched_mandate = twikey.document.fetch(
FetchMandateRequest(
mandate_number=mandate_number,
force=True,
)
)
```
```cs
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);
}
}
```
**Response**
```json
{
"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
| Name | Description | Required | Type | Max. |
|--------|--------------------------------|----------|---------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| force | Also include non-signed states | No | boolean | |
### Response
| Code | Description |
|------|--------------------------------------------------------------------------------------------|
| 200 | Details of the mandate (see /creditor/mandate) |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
| 429 | Too many requests |
### Header Response
| Header | Description |
|----------------------------------|---------------------------------------------------------------------------------------------|
| X-STATE | State of the mandate |
| X-COLLECTABLE | Whether this mandate can be used for collections (true/false) |
### Possible states
| State | Description |
|----------------------------|---------------------------------------------------------------------------------------------|
| PREPARED | User created the mandate, but the mandate is not signed yet |
| SIGNED | Client signed the mandate |
| EXPIRED | Mandate is beyond it's final date |
| CANCELLED | Mandate has been revoked |
| SIGNED_PENDING_DEBTOR_BANK | only in case of B2B - debtor bank needs to validate the mandate |
| REFUSED_BY_DEBTOR_BANK | only in case of B2B - signed but debtor bank refused the mandate |
| REQUIRES_MORE_SIGNATURES | mandate needs to be signed by a 2nd person - option needs to be activated on template level |
| PRINT | only in case of B2B - mandate has been printed by debtor |
| PENDING_MERCHANT_APPROVAL | mandate has been uploaded by debtor but needs to be validated by merchant |
### Possible events
| Event | Description |
|---------------|-------------------------------------|
| Cancel | depending on the state of a mandate |
| Accepted Bank | depending on the state of a mandate |
| Reject Bank | depending on the state of a mandate |
| Sign | depending on the state of a mandate |
| Print | depending on the state of a mandate |
| Uploaded | depending on the state of a mandate |
| Debtor Upload | depending on the state of a mandate |
| Expired | depending on the state of a mandate |
### Error codes
| Code | Description |
|-------------------|-----------------------------------------------------|
| err_no_contract | No contract was selected or invalid mndtId supplied |
| err_no_contract | No contract was found |
| err_invalid_state | Contract 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.
```bash
curl -X 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'
```
```php
$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;
```
```javascript
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));
```
```java
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();
```
```python
import twikey
ct = 1
twikey.document.update(
invite.mandate_number,
UpdateMandateRequest(
ct=ct,
state="active",
mobile="+32499000001",
iban="BE51561419613262",
bic="GKCCBEBB",
customer_number="CUST001",
email="joe.doe@gmail.com",
first_name="John",
last_name="Doe",
company_name="Acme Corp",
coc="BE0123456789",
l="en",
address="Main Street 1",
city="Brussels",
zip="1000",
country="BE",
)
)
```
```cs
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.
| Name | Description | Required | Type | Max. |
|----------------|-----------------------------------------------------------------------|----------|--------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| ct | Move the document to a different template ID (of the same type) | No | number | |
| state | active or passive (activated or suspend mandate) | No | String | 7 |
| mobile | Customer's mobile number to avoid errors use E.164 format | No | String | 50 |
| iban | Debtor's IBAN | No | String | 35 |
| bic | Debtor's BIC code | No | String | 11 |
| customerNumber | The customer number (can be added, updated or used to move a mandate) | No | string | 50 |
| email | email address of debtor | No | String | 70 |
| firstname | Firstname of the debtor | No | String | 50 |
| lastname | Lastname of the debtor | No | String | 50 |
| companyName | Company name on the mandate | No | String | 140 |
| coc | The enterprise number (can only be changed if companyName is changed) | No | String | 50 |
| l | language on the mandate | No | String | 2 |
| | To update the address all fields below are required | Yes | - | - |
| address | Address (street + number) | No | String | 70 |
| city | City of debtor | No | String | 50 |
| zip | Zipcode of debtor | No | String | 12 |
| country | ISO format | No | String | 2 |
### HTTP Response
| Code | Description |
|------|------------------------------------------------------------------------------------------------------------------------------------|
| 204 | The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|---------------------|--------------------------------------------------------------------------------------------------|
| err_no_contract | No contract was found |
| err_invalid_country | Invalid country code or not in ISO format (2 letters) |
| err_invalid_state | mandate 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.
* The email address is updated on the mandate using the new owner's one.
* The mobile number is updated on the mandate using the new owner's one.
* Address and name are preserved on the mandate.
* payment links and invoices remain stored on the original owner.
This can be done for both pending and signed mandates.
## 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.
```bash
curl -X POST https://api.twikey.com/creditor/customeraccess \
-H 'authorization: **authorization**' \
-d 'mndtId=mndtId123'
```
```php
$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);
?>
```
```javascript
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);
});
```
```java
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();
}
}
```
```python
import twikey
mandate_number = "MNDT123"
access_url = twikey.document.customer_access(mandate_number)
```
```cs
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);
}
}
```
**Response**
```json
{
"token": "A4DE98FD091....",
"url": "https://merchant.twikey.com/p/customeraccess?token=A4DE98FD0915F"
}
```
### HTTP Request
`POST https://api.twikey.com/creditor/customeraccess`
### Query Parameters
| Name | Description | Required | Type | Max. |
|--------|-------------------|----------|--------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
### HTTP Response
| Code | Description |
|------|-------------------|
| 200 | Request succeeded |
| 400 | Bad Request |
### Error Codes
| Name | Description |
|-----------------|-----------------------|
| err_no_contract | No contract was found |
## Retrieve mandate PDF
Retrieve the PDF that is available for the mandate.
```bash
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
```
```php
$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);
```
```javascript
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);
});
```
```java
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();
}
}
```
```python
import twikey
retrieved_pdf = twikey.document.retrieve_pdf("MANDATENUMBER")
retrieved_pdf.save("/tmp/pdf.pdf")
```
```cs
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
| Name | Description | Required | Type | Max. |
|--------|-------------------|----------|--------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
### HTTP Response
| Code | Description |
| ---- | ----------- |
| 200 | PDF is available in the body of the response |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
| ---- | ----------- |
| err_no_contract | No 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)
```bash
curl -X POST https://api.twikey.com/creditor/mandate/pdf \
-H 'authorization: **authorization**' \
-d 'mndtId=mndtId123' \
-d @mndtId123.pdf
```
```php
$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);
```
```javascript
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);
});
};
```
```java
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);
}
}
}
```
```python
import twikey
mandate_number = "MNDT123"
twikey.document.upload_pdf(
PdfUploadRequest(
mandate_number=mandate_number,
pdf_path=os.environ["PDF_FILE"],
bank_signature=False,
)
)
```
```cs
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
| Name | Description | Required | Type | Max |
|---------------|-----------------------------------------------|----------|--------|-----|
| mndtId | Mandate Reference | Yes | string | 35 |
| bankSignature | Includes the bank signature (true by default) | No | string | |
### HTTP Response
| Code | Description |
|------|--------------------------------------------------------------------------------------------|
| 200 | Import of the pdf was done |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|------------------|--------------------------------|
| err_no_contract | Contract not found |
| err_invalid_iban | no (valid) iban on the mandate |
## Retrieve legal terms
Retrieve legal terms
```bash
curl https://api.twikey.com/creditor/legal?locale=nl_BE \
-H 'authorization: authorization'
```
```php
$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);
```
```javascript
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)
});
```
```java
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();
}
}
```
```cs
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);
}
}
```
**Response**
```json
{
"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**
| Name | Description | Required | Type |
|--------|-----------------------------------------------------------------|---------------------|--------|
| locale | the locale (fr, fr_FR, fr_BE, de, nl, nl_BE, nl_NL, es, pt, it) | no (defaults to en) | string |
**Responses**
| Code | Description |
|------|---------------------------|
| 200 | The 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:
>
```bash
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`
**Response**
```json
{
"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
| Name | Description | type | max. length |
|-----------------|-----------------------------------------------------|--------|-------------|
| Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 |
### Query Parameters
| Name | Description | Required | Type | Max. |
|------------|------------------------------------------------------------|----------|--------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| message | Message to the subscriber | Yes | string | 140 |
| plan | Name of the base plan | No | string | |
| ref | Reference of the subscription (important for updates) [*1] | No | string | |
| amount | Amount of the transaction | Yes | number | |
| stopAfter | Number of times to execute | No | number | |
| recurrence | 1w / 2w / 1m / 2m / 3m / 4m / 6m /12m (default 1m) | No | string | |
| start | Start of subscription eg. 2022-11-01 (future date only) | Yes | date | |
[*1]: Max 140 characters.
### HTTP Response
| Code | Description |
|------|----------------------------------------------------------------------------------------------------------------------------|
| 200 | The server has fulfilled the request |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
| 409 | Conflict. A request with this Idempotency-Key is still processing; a retry after completion returns the original response. |
### Error codes
| Code | Description |
|--------------------|----------------------------------------------------------------------------------------|
| err_no_contract | No contract was found |
| err_invalid_date | Invalid start date |
| err_plan_range | Invalid stopAfter value |
| err_msg_missing | Message not supplied |
| err_invalid_amount | Invalid amount |
| err_invalid_params | Some parameter is invalid, see the _extra_ in the response body for more information. |
| err_duplicate_ref | Idempotency-key was already used in the last 24hrs. |
| err_invalid_state | The related contract is not in a collectable state |
## Update a Subscription
This endpoint allows you to create a new subscription based on an existing one. The current subscription is cancelled and a new one is created using the same `reference`.
```bash
curl -X POST https://api.twikey.com/creditor/subscription/:agreement/:ref \
-H 'authorization: **authorization**' \
-d 'mndtId=MNDT123' \
-d 'start=2025-05-05' \
-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
**Response**
```json
{
"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
| Name | Description | Required | Type | Max |
|------------|----------------------------------------------------------------------------|----------|--------|-----|
| mndtId | Mandate Reference (moves subscription if different from current) | Yes | string | 35 |
| start | Start date of the new subscription (must be a future date) | Yes | date | – |
| message | Message to the subscriber | Yes | string | 140 |
| amount | Transaction amount | Yes | number | – |
| recurrence | Frequency: 1w / 2w / 1m / 2m / 3m / 4m / 6m / 12m (default: 1m if not provided) | No | string | – |
| stopAfter | Number of executions (default: 0 if not provided) | No | number | – |
| plan | Base plan name (overrides message, amount, recurrence, and stopAfter) | No | string | – |
---
### Notes
- If you intend **not** to cancel the current subscription, consider using our [PATCH Subscription](#patch-a-subscription) request instead.
### HTTP Response
| Code | Description |
| ---- | ----------- |
| 200 | The request has succeeded. Returns the new Subscription object (same shape as the Response example above). |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|--------------------|-------------------------|
| err_no_contract | No contract was found |
| err_invalid_date | Invalid start date |
| err_plan_range | Invalid stopAfter value |
| err_msg_missing | Message not supplied or invalid |
| err_invalid_amount | Invalid amount |
| err_missing_params | mndtId is not correct |
| err_invalid_mandatenumber | mndtId contains non sepa characters |
## Patch a subscription
To update a subscription but not cancel it, you can use this endpoint. It allows you to update specific fields or move the subscription to a different mandate.
```bash
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
**Response**
```json
{
"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"
}
```
### URL Query Parameters
| Name | Description | Required | Type | Max. |
|---------|----------------------------------------------|----------|--------|------|
| mndtId | Move the subscription to a different mandate | No | string | 35 |
| message | Message to the subscriber | No | string | 140 |
| amount | Amount of the transaction | No | number | |
### HTTP Response
| Code | Description |
|------| ----------- |
| 200 | The server has fulfilled the request |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
|---------------------------|-------------------------------------------|
| err_no_contract | No contract was found |
| err_invalid_amount | Invalid amount |
| err_not_found | Subscription doesn't exist |
| err_uncollectable_contract | Target mandate/contract is not collectable |
| err_invalid_sepachars | Invalid characters in message |
## Cancel a subscription
A subscription can be cancelled by using it's ref for a specific agreement.
```bash
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
| Code | Description |
| ---- | ----------- |
| 204 | The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta-information |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
| ---- | ----------- |
| err_not_found | No subscription found (agreement, ref or combination incorrect) |
## Retrieve a single subscription
A single subscription can be fetched for a specific agreement.
This request is rate limited.
```bash
curl https://api.twikey.com/creditor/subscription/:agreement/:ref \
-H 'authorization: **authorization**'
```
### HTTP Request
`GET https://api.twikey.com/creditor/subscription/:agreement/:ref`
**Response**
```json
{
"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
| Parameter | Description | Required |
|------------------------|-----------------------------------------------------------|-----------|
| agreement reference | The reference of your agreement (eg. MNDT123) | Yes |
| subscription reference | The unique reference of a subscription for that agreement | Yes |
### HTTP Response
| Code | Description |
|------| ----------- |
| 200 | The server has fulfilled the request |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
| ---- | ----------- |
| err_not_found | No subscription found (agreement, ref or combination incorrect) |
### Include parameter
You can use the additional query parameter `include=customer` on any subscription response (create/get/query/replace/patch/action) to add a nested `customer` object (id, email, firstname, lastname, address, city, zip, country, customerNumber, lang, mobile, companyName, coc).
## Query all subscription
All subscription can be retrieved based on some criteria.
This request is rate limited.
```bash
curl https://api.twikey.com/creditor/subscription/query?mndtId=mdntId123&customerNumber=123&state=active \
-H 'authorization: **authorization**'
```
**Response**
```json
{
"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
| Name | Description | Required | Type |
|----------------|------------------------------------------------------------------|----------|--------|
| mndtId | Mandate Reference | No | string |
| customerNumber | Subscriptions by customerNumber | No | string |
| state | State of the subscription (active, suspended, cancelled, closed) | No | string |
| page | Page of the results (if more than 1 is available) | No | number |
### HTTP Response
| Code | Description |
|------| ----------- |
| 200 | The server has fulfilled the request |
| 400 | User 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.
```bash
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'
**Response**
```json
{
"id": 10,
"state": "suspended",
"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": "MNDT123"
}
```
### HTTP Response
| Code | Description |
| ---- | ----------- |
| 200 | The request has succeeded. Returns the updated Subscription object. |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
### Error codes
| Code | Description |
| ---- | ----------- |
| err_not_found | No subscription found (agreement, ref or combination incorrect) |
| err_invalid_params | Invalid parameter |
# Transactions
## Create 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`
```bash
curl -X POST https://api.twikey.com/creditor/transaction \
-H 'authorization: **authorization**'\
-d 'mndtId=mndtId123' \
-d 'message=Monthly payment' \
-d 'amount=10'
```
```php
$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);
```
```javascript
const transaction = await client.transaction.create({
mndtId: "CORERECURRENTNL16318",
message: "Test message",
amount: 500,
});
```
```java
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();
};
}
```
```python
import twikey
tx = twikey.transaction.create(
NewTransactionRequest(
mndt_number = "mandate_number",
message = "Test Message",
ref = "Merchant Reference",
amount = 10.00,
place = "Here",
)
)
```
```cs
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);
}
}
```
**Response**
```json
{
"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
| Name | Description | type | max. length |
|-----------------|-----------------------------------------------------|--------|-------------|
| Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 |
### Request Parameters
| Name | Description | Required | Type | Max. |
|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| date | Date of the transaction or now when empty | No | date | |
| reqcolldt | Requested collection date of the transaction or _null_ to collect as soon as possible | No | date | |
| message | Message to the customer [*1] | Yes | string | 140 |
| ref | Your reference | No | string | |
| amount | Amount to be billed | Yes | decimal | |
| place | Optional place | No | string | |
| refase2e | The reference is used as E2E identifier for the first payment
Conform with the Rule Book of the **[EPC](https://www.europeanpaymentscouncil.eu)**. | No | boolean | |
[*1]:
* This is the message that both you and your customer will see on their bank statement. [More info..](/r/admin#/c/reportingFiles)
* **Belgium**: The message is treated as structured message (OGM) when it is 12-characters long and the check-sum is correct.
### HTTP Response
| Code | Description |
|------|----------------------------------------------------------------------------------------------------------------------------|
| 200 | The request has succeeded |
| 400 | User error if parameter is given but not valid (available in apierror header and response) |
| 409 | Conflict. A request with this Idempotency-Key is still processing; a retry after completion returns the original response. |
### Error codes
| Code | Description |
|-----------------------|----------------------------------------------------------------------------------------|
| err_no_contract | No mandate found |
| err_invalid_state | The mandate is not active |
| err_invalid_date | Invalid Date |
| err_invalid_sepachars | Invalid characters in message to debtor |
| err_invalid_amount | Invalid Amount given |
| err_billing_overdrawn | Maximum amount reached according to risk rules |
| err_invalid_params | Some parameter is invalid, see the _extra_ in the response body for more information. |
| err_duplicate_ref | Idempotency-key was already used in the last 24hrs. |
## Bulk create transactions
```bash
curl -X POST https://api.twikey.com/creditor/transaction/bulk \
-H 'authorization: **authorization**' \
-H "Content-Type: application/json" \
-d '
[
{
"mndtId": "TWK009",
"msg": "testing",
"date": "2025-10-25",
"amount": "10.00",
"ref": "ref1"
},
{
"mndtId": "TWK009",
"msg": "testing2",
"collection_date": "2025-10-20",
"amount": "12.00",
"ref": "ref2"
}
]'
```
**response**
```json
{
"batchId": "bulk-tx-1-Dli3i3d4b1eBd"
}
```
This API request allows for the creation of multiple transactions in a single request by submitting an array of transaction objects. A
`batch ID` is returned, which can be used to query the status of the bulk operation. You will receive a webhook when the batch has been processed.
Fetching the response will return you the id's of the transaction with the corresponding mandate and reference (status 200), or in case one or
multiple transactions were rejected (status 400) the whole batch is rejected and you'll receive the list of all transactions with their corresponding
mandate/reference so you can either resubmit after handling/omitting the error cases.
**Details:**
* **Bulk Upload:** Submit an array containing up to 5,000 transactions objects in a single API call.
* **Batch Tracking:** A `GET` request to the Bulk Batch Details endpoint using the returned `batch ID` provides status and information for the submitted batch.
### HTTP Request
`POST /creditor/transaction/bulk`
### Request Parameters
The body must be a valid JSON array containing up to maximum 5,000 transaction objects.
> **Note:** The bulk endpoint uses JSON field names that differ from the form-encoded [Create transaction](#new-transaction) endpoint.
> Use `msg` (not `message`) and `collection_date` (not `reqcolldt`).
| Name | Description | Required | Type | Max. |
|-----------------|-----------------------------------------------------------------------------|----------|---------|------|
| mndtId | Mandate Reference | Yes | string | 35 |
| msg | Message to the customer (shown on bank statement) | Yes | string | 140 |
| amount | Amount to be billed | Yes | decimal | |
| ref | Your reference | No | string | |
| date | Date of the transaction, defaults to now when empty | No | date | |
| collection_date | Requested collection date, or _null_ to collect as soon as possible | No | date | |
| place | Optional place | No | string | |
| refase2e | Use reference as E2E identifier for the first payment | No | boolean | |
### HTTP Response
| Code | Description |
|------|---------------------------|
| 200 | The request has succeeded |
| 400 | Bad request (user-error) |
### Error codes
| Code | Description |
|--------------------|-----------------------------------|
| err_invalid_params | Body is malformed (no valid JSON) |
## Bulk batch details
This endpoint allows you to check the status of a bulk transaction upload.
You can use it to **poll the state** of an ongoing upload, or when the webhook was received to retrieve the final result once processing is complete.
### HTTP Request
`GET /creditor/transaction/bulk?batchId=123-456-789-123`
### HTTP Response
- **`409 Conflict`**
The upload is still being processed. No response body is returned.
You can continue polling until a final result is available.
- **`200 OK`**
The upload has completed successfully.
The response body contains an array of transaction IDs with their corresponding import status.
- **`400 Bad Request`**
The upload has failed.
The response body contains an array of transaction references, each marked with 'OK' or a failure status.
**Note:** In this case, *none* of the transactions from the batch are imported.
```bash
** GET Request
curl https://api.twikey.com/creditor/transaction/bulk?batchId=fe24ae78-6f46-46e6-bf8d-ef56da697121 \
-H 'authorization: **authorization**' \
-H "Content-Type: application/json" \
```
```json (200 response) [ { "id": 123, "ref": "ref1", "mndtId": "TWK009", "status": "OK" }, { "id": 124, "ref": "ref2", "mndtId": "TWK009", "status": "OK" } ] ``` ```json (400 response) [ { "ref": "ref1", "mndtId": "TWK009", "status": "OK" }, { "ref": "ref2", "code": "err_invalid_date", "extra": "2025-10-20T10:50:31.947979Z", "mndtId": "TWK009", "status": "ERROR" } ] ``` ### Request Parameters | Name | Description | Required | Type | |---------|---------------------------------------------------|----------|------| | batchId | the ID of the batch, returned in the POST request | yes | uuid | ### HTTP Response | Code | Description | |------|---------------------------------------| | 200 | The request has succeeded | | 409 | Conflict. Upload is still in progress | | 400 | Bad request. Bulk upload failed | ### Error codes | Code | Description | |---------------|-----------------------------| | err_not_found | Batch ID not found or empty | ## 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). ```bash curl https://api.twikey.com/creditor/transaction \ -H 'authorization: **authorization**' ``` ```php $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_HTTPHEADER, 'Authorization : $authorization'); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript const feed = await client.transaction.feed(); for await (const tx of feed) { console.log("Updated transaction: ",tx) } ``` ```java 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(); }; } ``` ```python import twikey import twikey.Transaction class MyFeed(twikey.TransactionFeed): def transaction(self, transaction: Transaction): state = transaction.state final = transaction.final ref = transaction.ref if not ref: ref = transaction.msg _state = state _final = "" if state == "PAID": _state = "is now paid" elif state == "ERROR": _state = f"failed due to '#{transaction.bkmsg}'" if final: # final means Twikey has gone through all dunning steps, but customer still did not pay _final = "with no more dunning steps" print(f"Transaction update #{transaction.amount} euro with #{ref} #{_state} #{_final}") twikey.transaction.feed(MyFeed()) ``` ```cs 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); } } ``` **Response** ```json { "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 }, "..." ] } ```Sample response:
**Response** ```json { "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 name | Description | |----------------|--------------------------------------------- | | X-RESUME-AFTER | Resume the feed after a specific sequence id | ### 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` | Value | Description | |--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **collection** | returns the batch id | | **lastupdate** | returns the last update in datetime format and 'bkdate' as date. | | **action** | returns all the dunning steps taken for a transaction | | **action_payment** | returns 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 | | **link** | returns the url of the dunning payment link (when in dunning) | | **stage** | returns the the current final stage of the transaction | | **seq** | return a sequence ID in the feed | ### Dunning types * **FAIL_SOFT**: Action(s) when insufficient funds * **FAIL_HARD**: Action(s) when customer refuses * **TECHNICAL**: Action(s) on failure at bank ### Dunning actions * **notify**: notification sent to the customer * **backup**: alternative payment proposed to the customer * **again**: re-offer the transaction. * **state**: changed state of the mandate * **wik**: official WIK letter sent * **paid**: partial payment registered with `amount` as decimal value ### Transaction stage values * **NONE**: failed and still in dunning. * **UNSETTLED**: failed and requires manual action * **WIKLETTER**: official WIK letter sent * **AGENCY**: sent to the collection agency * **ARCHIVED**: planned dunning steps are stopped (those in progress are not) ### admincharge This is returned when a transaction failed and administrative charges were applied. ### bkamount The **bkamount** is the amount of the transaction that was (partially) paid. This value is increased with each partial payment up to a maximum of the total amount. ### Possible states | Possible states | Description | | ---- | ----------- | | PAID | The transaction has been executed + final flag info| | ERROR |The 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 states | Description | | ---- | ----------- | | TRUE | No more actions are pending. Manual action is required if the state is in error | | FALSE | More actions are pending to debit the debtor's account | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | ### Error codes | Code | Description | | ---- | ----------- | | err_missing_params | No parameter given | | err_no_transaction | No link found based on the transaction | | err_not_found | No link found | | err_call_in_progress | Request already in progress by another client | ### Response parameters | Name | Description | |------------|------------------------------------------------------------------------| | date | Date when the transaction was created | | bkdate | Date when the transaction was booked (received feedback from the bank) | | reqcolldt | The requested collection date | | bkerror | Error code when a transaction failed | | bkamount | The amount already paid | | collection | The 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](#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. ```bash curl https://api.twikey.com/creditor/transaction/detail?id=1234 \ -H 'authorization: **authorization**' ``` ```php $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, CURLOPT_HTTPHEADER, array('Authorization: ' . $authorization)); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey tx = twikey.transaction.status_details( StatusRequest( mandate_number="mandate_number", state="ERROR", include=["collection", "lastupdate", "links"] ) ) ``` ```cs 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); } } ``` **Response** ```json { "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" } ] } ```Including all parameters:
**Response** ```json { "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 | Name | Description | Required | Type | |--------|----------------------|----------|--------| | id | id of a transaction | No | string | | ref | ref of a transaction | No | string | | mndtId | mandate reference | No | string | ### 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` | Name | value | Description | Type | | ---- |-------|-------------------------------------------------| ---- | | state | OPEN | only return open transactions [*1] | string | | state | PAID | only return paid transactions | string | | state | ERROR | only return transactions in an error state | string | | state | UNPAID| only return transactions in an open/error state | string | * [*1]: The state is 'OPEN' when: * When the transaction is not yet sent to the bank. The transaction can still be deleted * When the transaction is already sent to the bank, but no feedback was received. The transactions can't be deleted ### 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` | Name | Value | Description | | ---- | ----------- | -------- | | include | collection | returns the batch id | | include | lastupdate | returns the last update in datetime format and 'bkdate' as date. | | include | link | returns 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_. * **_true_**: There are no more pending actions. If the transaction is in the state ERROR then a manual action is required. * **_false_**: At least one action is still pending for the transaction. ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 429 |Too many requests | ### Error codes | Code | Description | | ---- | ----------- | | err_invalid_params | Either 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` ```bash curl -X POST https://api.twikey.com/creditor/transaction/action \ -H 'authorization: **authorization**'\ -d 'id=345' \ -d 'action=reoffer' ``` ```php $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); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey twikey.transaction.action( ActionRequest( id="6302230", action="archive", ) ) ``` ```cs 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 | Name | Description | Required | Type | Options | |--------|---------------------------------------------|----------|--------|-------------------------------------------| | id | Transaction id | Yes | string | | | action | Action to execute for the given transaction | Yes | string | paid, 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 | Code | Description | |------| ----------- | | 204 | The request has succeeded | | 201 | Deletion of the transaction has succeeded | | 400 | User error if parameter is given but not valid (available in api error header and response) | ### Error codes | Code | Description | |--------------------|------------------------------------------------------| | err_no_transaction | No transaction found | | err_no_contract | No mandate found (or not active) | | err_invalid_params | one of the parameters provided contains invalid data | ## Update a transaction Update parameters for an existing transaction. ### HTTP Request `PUT /creditor/transaction` ```bash curl -X PUT https://api.twikey.com/creditor/transaction \ -H 'authorization: **authorization**'\ -d 'id=345' \ -d 'message=Monthly payment' \ -d 'amount=10' ``` ```php $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); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey twikey.transaction.update( UpdateRequest( id="tx_id", message="Test Message", ref="Merchant Reference", amount=10.00, place="Here", ) ) ``` ```cs 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 | Name | Description | Required | Type | |-----------|--------------------------------------|----------|--------| | id | Transaction id | Yes | string | | reqcolldt | Requested date of the billable event | No | string | | message | Message to the customer | No | string | | ref | Your reference | No | string | | amount | Amount to be billed | No | string | | place | Optional place | No | string | ### HTTP Response | Code | Description | |------|---------------------------------------------------------------------------------------------| | 204 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in api error header and response) | ### Error codes | Code | Description | | ---- | ----------- | | err_no_transaction | No transaction found | | err_no_contract | No mandate found (or not active) | | err_invalid_params | one of the parameters provided contains invalid data | | err_invalid_date | Invalid Date | | err_invalid_amount | Invalid 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. ```bash 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' ``` ```php $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); ``` ```javascript 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); }); ``` ```java 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(); } ; } ``` ```python import twikey refund = twikey.transaction.refund( RefundRequest( id="PAID_TX_ID", message="Test message", amount=50.00, ) ) ``` ```cs 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 | Name | Description | type | max. length | |-----------------|-----------------------------------------------------|--------|-------------| | Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 | ### Request Parameters | Name | Description | Required | Type | |---------|----------------------------------------------------------------------------|----------|--------| | id | The transaction id | Yes | String | | message | Message for the refund | Yes | String | | amount | Amount to be refunded | Yes | Number | | ref | Add a reference for the refund | No | String | | place | Place of refund | No | String | | iban | Iban of the Beneficiary account (optional otherwise iban from the mandate) | No | String | | bic | Bic of the Beneficiary account | No | String | **Response** ```json { "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 | Code | Description | |------|--------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |--------------------|----------------------------------------------| | err_invalid_params | Missing request parameters or invalid values | | err_msg_missing | message parameter missing | ## Remove a transaction Remove a transaction that wasn't sent to the bank yet based on the id and/or reference. ```bash curl -X DELETE https://api.twikey.com/creditor/transaction \ -H 'authorization: **authorization**' ``` ```php $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, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . $authorization)); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); } ; } ``` ```python import twikey twikey.transaction.remove( RemoveTransactionRequest( id="tx_id" ) ) ``` ```cs 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.** | Name | Description | Required | Type | |------|-------------------------------------------------------------------|----------|--------| | id | a transactionId as returned in the post | No | string | | ref | Transaction reference (ref) as provided in the post to be removed | No | string | ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 204 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |--------------------------------|---------------------------------------------------------| | err_no_contract | Contract not found | | err_not_found | Transaction not found | | err_transaction_invalid_action | Action invalid for the current state of the transaction | | err_duplicate_tx | Multiple transactions with the same reference exist | | err_not_authorised | When trying to delete a transaction that already went to the bank | ## 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](/api/#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. ```bash curl https://api.twikey.com/creditor/transaction/query?fromId=1000000 \ -H 'authorization: **authorization**' ``` ```python import twikey mandates = twikey.transaction.query( QueryTransactionsRequest( from_id="tx_id", ) ) ``` **Response** ```json { "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 | Name | Description | Required | Type | |--------|------------------------------------------------------------|----------|----------| | fromId | The Id of the transaction from where to start your query | Yes | Number | | mndtId | Mandate Reference | No | string | ### HTTP Response | Code | Description | |------|---------------------------| | 200 | The request has succeeded | | 400 | Bad request | | 429 | Too many requests | ### Error Codes | Code | Description | |--------------------|----------------------------------------------------------------------------------------------| | err_not_found | The ID provided was not found in the environment. The ID provided is included under *extra*. | | err_missing_params | Some 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` This endpoint supports idempotency. See [Idempotency Keys](#idempotency-keys). ```bash curl -X POST https://api.twikey.com/creditor/collect \ -d ct=123 \ -H 'authorization: **authorization**' ``` ```php $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&colltndt=2017-09-15" ); curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorization"); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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", "colltndt":"2017-09-15" } }; var req = https.request(options, function (res) { console.log("response: " + res) }); ``` ```java 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("colltndt","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(); } } ``` ```cs 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 + "&colltndt=2017-09-15" , ParameterType.RequestBody ); IRestResponse response = client.Execute(request); } } ``` ### Request Headers | Name | Description | type | max. length | |-----------------|-----------------------------------------------------|--------|-------------| | Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 | ### Query parameters | Name | Description | Required | Type | |----------|-----------------------------------------------------------------------------------------------------|----------|-----------| | ct | Contract template for which to do the collection | Yes | number | | colltndt | Collection date (default=earliest batch) **[*1]** | No | string | | until | Only include transactions in the batch created until a specific date - **epoch in milliseconds** | No | number | **[*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. **Response** ```json { "frstMsgId": null, "rcurMsgId": "BE81ZZZ08502098492203301148072234" } ``` ### Response Parameters A batch was created when `rcurMsgId` returns a value — this is the `pmtinfid` of the created batch, which can be passed to `GET /creditor/collect` to retrieve the full batch detail. When `rcurMsgId` is **_null_** nothing was sent (no open transactions found). | Name | Description | | ---- |-------------| | frstMsgId | Always `null` (deprecated, kept for backwards compatibility) | | rcurMsgId | The `pmtinfid` of the created batch, or `null` if nothing was sent | > **Webhook:** When a batch is successfully created, Twikey fires a `transaction/collection` webhook with the batch `id`. Use this instead of polling to react immediately. See [Webhooks](#webhooks). ### HTTP Response | Code | Description | |------|-----------------------------------------------------------------------------------------------------------------------------| | 200 | Request succeeded. The response returns the id referencing both first and recurring sdd send to bank | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 409 | Conflict. A request with this Idempotency-Key is still processing; a retry after completion returns the original response. | ### Error codes | Code | Description | |------------------------|----------------------------------------------------------| | err_no_such_ct | No template specified | | err_invalid_ct | Invalid template specified | | err_provide_account | No recurring gateway configured on the profile (ct) | | err_invalid_date | Invalid date | | err_invalid_params | Invalid parameters passed in the request | | err_call_in_progress | Request already in progress by another client | ## 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` ```bash curl https://api.twikey.com/creditor/collect \ -H 'authorization: **authorization**' ``` ```php $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"); $server_output = curl_exec ($ch); $result = json_decode($server_output); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); } } ``` ```cs 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); } } ``` **Response** ```json { "Sdds":[ { "id":1437939405111, "pmtinfid": "mypmtid", "ct": 1234, // profile id "tx":4, // Number of transactions "amount":120.0, // Total amount of the transactions "status": "Sent", // Sent, Archived or Cancelled "reqcolldt": "2015-07-27", // Date the batch is presented to the bank "generated": "2015-07-24", // Date the batch was created "progress": "sent", // download, sent, accept, signature, received, error, unknown "Entries": [ { "e2eid": "mye2e", "txid": 9001, "txref": "myref", "amount": 30.0, "contractId": 7488, // internal reference to the contract "mandateRef": "TWIKEYCORE22", // mandate reference "msg": "testing", "state": "open" // No payment information received as of yet }, { "e2eid": "mye2e", "txid": 9002, "txref": "myref", "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 }, { "e2eid": "mye2e", "txid": 9003, "txref": "myref", "amount": 30.0, "contractId": 6358, "mandateRef": "TWIKEYCORE21", "msg": "test", "final": false, // Twikey still has outstanding actions (dunning in progress) "state": "error", "bkamount": 0.0, "bkdate": "2015-07-27", "bkerror": "MD06", // Actual error code (refund request) "bkmsg": "Refund request by debtor" }, { "e2eid": "mye2e", "txid": 9004, "txref": "myref", "amount": 30.0, "contractId": 6358, "mandateRef": "TWIKEYCORE21", "msg": "test", "final": true, // Could not collect, please contact your customer "state": "error", "bkamount": 0.0, "bkdate": "2015-07-27", "bkerror": "AG01", "bkmsg": "Transaction forbidden" } ] } ] } ``` ### Query parameters | Name | Description | Required | Type | | ---- | ----------- | -------- | ---- | | id | Specific SDD reference | No | number | | pmtinfid | Specific Payment identifier | No | string | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 429 |Too many requests | ### Error Codes | Error | Description | | ----- | --------- | | err_not_found | No SDD batch found for the given id/pmtinfid | | err_missing_params | Neither id nor pmtinfid was provided | ## 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 formats: Pain.008.001.01, Pain.008.001.02 or Pain.008.001.08 (ISO 20022) A profile id needs to be passed using the `ct`query 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. ```bash 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' ``` ```php $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 => "Sample response when using multiple parameters:
**Response** ```json { "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 | Name | Description | Required | Type | |------|------------------------------------------------------------------------------|----------|---------| | all | Include all non-paid updates too (by default only paid updates are returned) | No | boolean | ### include parameter The '**include**' parameter can be added multiple times with different values | Value | Description | Required | Type | |----------|------------------------------------------------|----------|--------| | customer | customer detail | No | string | | meta | meta data | No | string | | time | time data | No | string | | refunds | return 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 | States | Description | |----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Created | The payment link has been created but the customer did not do anything with it yet. | | Started | The 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. | | Pending | The customer started to pay, but did not complete the payment process. (only for iDEAL \| Wero, Paypal & Tikkie) | | Declined | The customer completed the payment process but the payment wasn't successful | | Paid | The customer paid the amount asked. | | Expired | The 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. | Parameter | Description | |---------------|---------------------------------------------------------------------------------------------------------------------| | active | the payment link url is still valid (true, false) | | sdd | internal sdd identifier (Twikey) | | tx | transaction id returned when linked to a transaction | | method | the method of payment. Only returned when linked to an invoice or if explicitly passed when creating a paymentlink. | | paymentMethod | returns the used payment method when we received it from the payment provider | | type | the payment method returned by the PSP. Only returned if we received it (PSP dependant). | | invoice | invoice unique identifier returned when linked to an invoice | **time** | Parameter | Description | |------------|-------------------------------------------------------------| | creation | creation of the payment link | | expiration | expiry of the payment link. Not returned when there is none | | lastupdate | last 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 | Code | Description | |------|---------------------------| | 200 | The request has succeeded | ### Error Codes | Code | Description | |----------------------|-----------------------------------------------| | err_call_in_progress | Request already in progress by another client | ## Status paymentlink Get status of a payment link This request is rate limited. ### HTTP Request `GET /creditor/payment/link` ```bash curl https://api.twikey.com/creditor/payment/link?id=123 \ -H 'authorization: **authorization**' ``` ```php $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); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey pl = twikey.paylink.status_details( PaymentLinkStatusRequest(id="644722") ) ``` ```cs 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); } } ``` **Response** ```json [{ "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**, **ref** or **tx** is mandatory. | Name | Description | Required | Type | |------|---------------------------------------------------------|----------|--------| | id | Id of the link | No | string | | ref | Your reference | No | string | | tx | Transaction id to look up the payment link linked to it | No | string | ### 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: | Parameter | Description | |---------------|---------------------------------------------------------------------------------------------------------------------| | active | the payment link url is still valid (true, false) | | sdd | internal sdd identifier (Twikey) | | tx | transaction id returned when linked to a transaction | | method | the method of payment. Only returned when linked to an invoice or if explicitly passed when creating a paymentlink. | | paymentMethod | returns the used payment method when we received it from the payment provider | | type | the payment method returned by the PSP. Only returned if we received it (PSP dependant). | | invoice | invoice unique identifier returned when linked to an invoice | | time | return date and time events (creation, last updated, expiry) | | recurringId | recurring/token identifier returned by the PSP for recurring or tokenized card payments, when available | | expiry | the card's expiry date, when paid by card and returned by the PSP (distinct from `time.expiration`) | ### 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 | States | Description | |----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | created | The payment link has been created but the customer did not do anything with it yet. | | started | The 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. | | pending | The customer started to pay, but did not complete the payment process. (only for iDEAL \| Wero, Paypal & Tikkie) | | declined | The customer completed the payment process but the payment wasn't successful | | paid | The customer paid the amount asked. | | expired | The customer did not complete the payment before the expiration date you defined when creating the payment link. | ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 429 | Too many requests | ### Error codes | Code | Description | |--------------------|----------------------------------------| | err_missing_params | No parameter given | | err_no_transaction | No link found based on the transaction | | err_not_found | No link found | ## Refund paymentlink Refund the full or partial amount of a payment link. ```bash curl -X POST 'https://api.twikey.com/creditor/payment/link/refund' \ -H 'authorization: **authorization**'\ -D 'id=123' \ -D 'message=refund' \ -D 'amount=25' ``` ```python import twikey refund = twikey.paylink.refund( PaymentLinkRefundRequest( id="PAID_PAYLINK_ID", message="hello", iban="BE51561419613262", bic="GKCCBEBB", ) ) ``` ### HTTP Request `POST /creditor/payment/link/refund` ### Request Headers | Name | Description | type | max. length | |-----------------|-----------------------------------------------------|--------|-------------| | Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 | ### Supported PSP | Name | Supported | |----------------|-----------| | Mollie | ✅ | | MultiSafePay | ✅ | | Buckaroo | ✅ | | Adyen | ✅ | | Rabo Smart Pay | ✅ | | CCV | ✅ ℹ️ | | PayNL | ✅ | | ING Checkout | ✅ | ℹ️ Only when a specific `method` was passed when creating the payment link. ### Request Parameters | Name | Description | Required | Type | |---------|-------------------------------------------------------------|----------|--------| | id | Paymentlink ID (either `id` or `ref` is mandatory) | No | string | | ref | Your reference (either `id` or `ref` is mandatory) | No | string | | message | Refund message | Yes | String | | amount | Full or partial amount (if not passed, full amount is used) | No | number | ```json { "id": 10.23, "amount": 25.0, "msg": "refund" } ``` ### HTTP Response | Code | Description | |------|----------------------------------------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | Failure when trying to refund the payment link | | 409 | Conflict. A request with this Idempotency-Key is still processing; a retry after completion returns the original response. | ### Error codes | Code | Description | |---------------------|-------------------------------------------------------------------| | err_contact_support | Error details in response "extra" | | err_fail_refund | Error details in response "extra" | | err_missing_params | No `id` or `ref` given to identify the payment link | | err_not_found | No payment link found for the given `id`/`ref` | | err_msg_missing | Refund message is missing or exceeds the allowed length | | err_invalid_amount | Refund amount is missing, non-positive, or otherwise invalid | | err_invalid_state | The payment link has not been paid yet | | err_provide_account | No valid IBAN/BIC provided and native PSP refund is not available | ## Remove paymentlink Remove a payment link. Only paymentlinks with status 'created' can be removed, all other ones will be archived. ```bash curl -X DELETE https://api.twikey.com/creditor/payment/link?id=123 \ -H 'authorization: **authorization**' ``` ```python import twikey twikey.paylink.remove(link_id="pl_id") ``` ### HTTP Request `DELETE /creditor/payment/link` ### Request Parameters | Name | Description | Required | Type | |------|----------------------------------------------------|----------|--------| | id | paymentlink ID (either `id` or `ref` is mandatory) | No | string | | ref | Your reference (either `id` or `ref` is mandatory) | No | string | ## Meta data Including meta data when you retrieve the status of one (or more) payment link(s) provides you with the following information: * If the link was generated in a dunning step. * If the link was generated in order to get an invoice paidSideloading customer, meta and time:
**Response** ```json { "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 | Name | Description | |---------|----------------------------------------------------------------------------------------| | tx | The transaction for which this link was generated | | sdd | The sdd transaction (state = failed) send to the bank for wich this link was generated | | invoice | The 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](#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 '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` ```bash 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' ``` ```php $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); ``` ```javascript 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) }); ``` ```java 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(); } } ``` ```python import twikey refund = twikey.refund.create( NewRefundRequest( customer_number="customer_number", iban="NL46ABNA8910219718", message="Refund faulty item", ref="My internal reference", amount=10.99, ) ) ``` ```cs 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); } } ``` **Response** ```json { "Entries": [ { "id": "11DD32CA20180412220109485", "iban": "BE68068097250734", "bic": "JVBABE22", "amount": 12, "msg": "test", "place": null, "ref": "123", "date": "2018-04-12" } ] } ``` ### Request Headers | Name | Description | type | max. length | |-----------------|-----------------------------------------------------|--------|-------------| | Idempotency-Key | Unique key usable only once per request every 24hrs | string | 64 | ### Request parameters | Name | Description | Required | Type | |----------------|----------------------------------------------------------|------------------------------|--------------| | customerNumber | The customer number (strongly recommended) | Yes | string | | iban | Iban of the beneficiary (must be active) | Yes (if it can't be derived) | string | | message | Message to the creditor | Yes | string (140) | | amount | Amount to be refunded | Yes | number | | ref | Reference of the transaction | No | String | | date | Required execution date of the transaction (ReqdExctnDt) | No | string | | place | Optional place | No | string | ### HTTP Response | Code | Description | |------|----------------------------------------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 409 | Conflict. A request with this Idempotency-Key is still processing; a retry after completion returns the original response. | ### Error codes | Code | Description | |-----------------------|----------------------------------------------------------------------------------------| | err_invalid_iban | Invalid Iban | | err_invalid_state | Beneficiary account was found but is disabled/inactive | | err_invalid_date | Invalid date | | err_invalid_sepachars | Invalid message | | err_msg_missing | Message is missing or exceeds 140 characters | | err_invalid_amount | Invalid amount | | err_not_found | Customer not found | | err_invalid_params | Some parameter is invalid, see the _extra_ in the response body for more information. | | err_fail_refund | Beneficiary account's IBAN is disabled and requires manual re-activation | ## 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. ```bash curl https://api.twikey.com/creditor/transfer \ -H 'authorization: **authorization**' ``` ```php $host = "https://api.twikey.com"; $authorization = null; // collected through login $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "$host/creditor/transfer"); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . $authorization)); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey class MyFeed(twikey.RefundFeed): def refund(self, refund: Refund): print(f"Refund update #{refund.id} {refund.amount} Euro with new state={refund.state}") twikey.refund.feed(MyFeed()) ``` ```cs 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` **Response** ```json { "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 | Parameter | Description | |---------------------------|-------------------------------------------------------| | include=seq | Return the sequence number in the feed for each entry. | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The 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` ```bash curl https://api.twikey.com/creditor/transfer/detail?id=609C16C920180919083905923 \ -H 'authorization: authorization' ``` ```php $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); ``` ```javascript 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) }); ``` ```java 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(); } } ``` ```python import twikey details = twikey.refund.details(refund_id="refund_id") ``` ```cs 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); } } ``` **Response** ```json { "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 | Name | Description | Required | Type | | ---- | ----------- | -------- | ---- | | id | id (e2e identifier) of the created refund| Yes | string | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | | 429 |Too many requests | ### Error codes | Code | Description | | ---- | ----------- | | err_no_transaction | No such transaction | ## Remove a credit transfer Remove a single credit transfer. ### HTTP Request `DELETE /creditor/transfer` ```bash curl -X DELETE https://api.twikey.com/creditor/transfer?id=123 \ -H 'authorization: **authorization**' ``` ```php $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, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . $authorization)); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey twikey.refund.remove(refund_id="refund_id") ``` ```cs 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 | Name | Description | Required | Type | | ---- | ----------- | -------- | ---- | | id | id of the created refund | Yes | string | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | | ---- | ----------- | | err_no_transaction | No 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` ```bash curl https://api.twikey.com/creditor/transfer/complete \ -H 'authorization: authorization' \ -d 'iban=BE53587499351234' ``` ```php $host = "https://api.twikey.com"; $iban = "BE53587499351234"; $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, "iban=$iban"); curl_setOpt($ch, CURLOPT_HTTPHEADER,"authorization: $authorisation"); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript var https = require('https'), iban = "BE53587499351234", 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: { "iban": iban } }; var req = https.request(options, function (res) { console.log("response: " + res) }); ``` ```java public class TwikeyApi{ private String host = "https://api.twikey.com"; private String iban = "BE53587499351234"; 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(); } } ``` ```python import twikey credit_transfers = twikey.refund.create_batch( NewRefundBatchRequest( iban="BE53587499351234", ) ) ``` ```cs public class TwikeyAPI { private String host ="https://api.twikey.com", iban = "BE53587499351234", 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", "iban=" + iban, ParameterType.RequestBody ); IRestResponse response = client.Execute(request); } } ``` ### Query parameters | Name | Description | Required | Type | |----------------|----------------------------------------------------------------------------------------------|----------|--------| | iban | Originating account, bank account with refunds enabled from your Payment Hub | Yes (if `ct` not given) | string | | ct | Contract template id — legacy alternative to `iban` for selecting the gateway | No | number | | date | Execution date for the batch. Must not be in the past (returns `err_invalid_date` otherwise) | No | date | | include | Pass `details` to return the full list of entries in the batch instead of just the count | No | string | **Response** ```json { "CreditTransfers": [ { "id": 2837, "pmtinfid": "Twikey-20220330113125070605075", "entries": 2 } ] } ``` ### HTTP Response | Code | Description | |------|---------------------------| | 200 | The request has succeeded | | 400 | Invalid request | ### Error codes | Code | Description | |-------------------|-----------------------------------------------------------------------------------| | err_invalid_iban | IBAN is not known or refunds are not enabled for that account in your Payment hub | | err_name_required | Customer(s) in the batch don't have a valid name | ## Batch details Get all details from a specific batch. Pass `include=details` to return the full list of entries instead of just the count. ### HTTP Request `GET /creditor/transfer/complete` ```bash curl https://api.twikey.com/creditor/transfer/complete?id=2837&pmtinfid=Twikey-20220330113125070605075 \ -H 'authorization: authorization' \ ``` ```php $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); ``` ```javascript 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) }); ``` ```java 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(); } } ``` ```python import twikey details = twikey.refund.batch_detail( RefundBatchStatusRequest( id="credit_transfers_batch_id" ) ) ``` ```cs 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 | Name | Description | Required | Type | | ---- | ----------- | -------- | ---- | | id | Id of the batch previously submitted | No | string | | pmtinfid | PmtInfId of the batch previously submitted| No | string | **Response** ```json { "CreditTransfers": [ { "id": 2837, "pmtinfid": "Twikey-20220330113125070605075", "progress": "download", "entries": 2 } ] } ``` ### Response Parameters | Name | Description| | ---- | ---------- | | id | Batch identifier | | pmtinfid | Payment identifier | | progress | progress of the batch: `download` (not yet retrieved), `sent` (transmitted to an integrated bank gateway, awaiting feedback), `received` (by bank) | | entries | credit transfers in this batch | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | Invalid request | ### Error codes | Code | Description | | ---- | ----------- | | err_invalid_params | No such batch available | ## Get beneficiary accounts Fetch a list of all customers' beneficiary accounts. ### HTTP Request `GET /creditor/transfers/beneficiaries` ```bash curl https://api.twikey.com/creditor/transfers/beneficiaries \ -H 'authorization: authorization' ``` ```php $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_HTTPHEADER,"authorization: $authorization"); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript var https = require('https'), host = "api.twikey.com", authorization = null, //collected through login options = { host: host, port: '443', path: '/creditor/transfers/beneficiaries', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; var req = https.request(options, function (res) { console.log("response: " + res) }); ``` ```java 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") .addHeader("content-type", "application/x-www-form-urlencoded") .addHeader("authorization", authorisation) .addHeader("cache-control", "no-cache") .build(); Response response = client.newCall(request).execute(); } } ``` ```python import twikey beneficiaries = twikey.refund.get_beneficiary_accounts() ``` ```cs 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.GET); request.AddHeader("authorization", authorisation); request.AddHeader("content-type", "application/x-www-form-urlencoded"); IRestResponse response = client.Execute(request); } } ``` **Response** ```json { "beneficiaries": [ { "name": "sdfsf", "ref": "customer123", "iban": "BE92221216720939", "bic": "GEBABEBB", "available": true, "address": null }, { "name": "beneficiary2", "ref": "", "iban": "BE16645348971174", "bic": "JVBABE22", "available": true, "address": { "street": "Veldstraat 11", "city": "Gent", "zip": "9000", "country": "BE" } } ] } ``` ### Query parameters | Name | Description | Required | Type | | ---- | ----------- | -------- | ---- | ### HTTP Response | Code | Description | | ---- | ------------------------- | | 200 | The request has succeeded | ## 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` ```bash 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' ``` ```php $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); ``` ```javascript 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) }); ``` ```java 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(); } } ``` ```python import twikey benef = twikey.refund.create_beneficiary_account( NewBeneficiaryRequest( customer_number="customer_number", email="info@twikey.com", name="Info Twikey", l="en", address="Abby road", city="Liverpool", zip="1526", country="BE", mobile="", iban="NL46ABNA8910219718", bic="ABNANL2A", ) ) ``` ```cs 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); } } ``` **Response** ```json { "name": "Beneficiary Name", "ref": "customer123", "iban": "BE68068897250734", "bic": "JVBABE22", "available": true, "address": { "street": "Veldstraat 11", "city": "Gent", "zip": "9000", "country": "BE" } } ``` ### Query parameters | Name | Description | Required | Type | Max. | |----------------|--------------------------------------------------------------------|----------------------------|--------|------| | customerNumber | The customer number | No | string | 50 | | name | Firstname & lastname of the debtor | No | string | | | email | Email of the debtor | No | string | 70 | | l | language of the customer ISO format (2 letters) | No | string | | | mobile | Mobile number required for sms (International format +32499123445) | No | string | 50 | | address | The street name and number/box | No (Yes for new customers) | string | 70 | | city | City of debtor | No (Yes for new customers) | string | 50 | | zip | Zipcode of debtor | No | string | 12 | | country | ISO format (2 letters) | No (Yes for new customers) | string | 2 | | companyName | The company name | No | string | 140 | | vatno | The enterprise number | No | string | 50 | | iban | IBAN of the beneficiary | Yes | string | 35 | | bic | BIC of the beneficiary | No | string | 11 | ### HTTP Response | Code | Description | | ---- | ----------- | | 200 | The request has succeeded | | 400 | Validation error due to wrong user input | ### Error codes | Code | Description | | ---- | ----------- | | err_name_required | Invalid or missing name | | err_invalid_iban | Invalid IBAN number | | err_invalid_bic | Invalid BIC number | | register_address_missing | Address missing | | err_fail_refund | This IBAN was previously disabled and requires manual re-activation via the interface before it can be added again | ## Disable a beneficiary account Disable of a beneficiary account can be done via an IBAN number. Passing `customerNumber` is **required** unless your account has the IBAN-only refund capability enabled — without it (the default), a request without `customerNumber` fails with `err_not_found`. Even when enabled, we strongly recommend always passing `customerNumber`, as an account may be used on multiple customers. Note that 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}` ```bash curl -X DELETE https://api.twikey.com/creditor/transfers/beneficiaries/BE16645348971174?customerNumber=123 \ -H 'authorization: **authorization**' ``` ```php $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, CURLOPT_CUSTOMREQUEST, 'DELETE'); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: ' . $authorization)); $server_output = curl_exec ($ch); curl_close ($ch); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```python import twikey twikey.refund.disable_beneficiary_accounts( DisableBeneficiaryRequest( iban = "NL46ABNA8910219718", customer_number = "customer_number" ) ) ``` ```cs 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 | Code | Description | | ---- | ----------- | | 204 | the request has succeeded, no content | | 400 | bad request | ### Error codes | Code | Description | | ---- | ----------- | | err_not_found | No such beneficiary (also returned when `customerNumber` is omitted without the IBAN-only refund capability enabled) | | err_invalid_iban | No beneficiary found for that IBAN | # 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/Sample:
**Response** ```json { "id":"2e708d7a-a4bf-4535-aea2-3ab24c179519", "mndtId":"MYMANDATE123", "reservedAmount":50.00, "expires":"2021-12-07T22:42:47Z", "scaUrl":"https://psp.example.com/authenticate?token=abc123" } ``` The `scaUrl` field is only present for RCC reservations when the PSP requires Strong Customer Authentication. When returned, redirect the cardholder to this URL before capturing. Twikey sends a webhook notification when SCA completes: * **Success** — the reservation is active; proceed to capture. * **Failure or abandoned** — the reservation is removed; create a new reservation to retry.Successful response (status=200):
**Response** ```json { "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. | Code | Description | |-----------------------|-----------------------------------------------------------------------| | err_billing_overdrawn | Maximum amount/number of transactions reached according to risk rules | | rule_amount | Maximum amount/number of transactions defined in the risk rule | | over_amount | Amount/number of transactions exceeding the risk rule | ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |-----------------------|--------------------------------------------| | err_no_contract | No mandate found | | err_invalid_state | The mandate is not active | | err_invalid_date | Invalid Date | | err_invalid_sepachars | Invalid characters in message to debtor | | err_invalid_amount | Invalid Amount given | | err_not_authorised | Reservation 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` ```bash curl -X DELETE https://api.twikey.com/creditor/reservation \ -H 'authorization: **authorization**'\ -H 'X-RESERVATION: **reservation id**' ``` ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 204 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |---------------|--------------------------| | err_not_found | Reservation 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 | Name | Description | Required | Type | |------|-------------|----------|--------| | ct | profile id | Yes | number | ```bash curl https://api.twikey.com/creditor/payment/methods?ct=1234 \ -H 'authorization: **authorization**'\ ```Or when a risk rule was hit (status=400):
**Response** ```json [ "bancontact", "maestro", "mastercard", "visa" ] ``` ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |----------------|-------------------| | err_no_such_ct | Profile 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` ```bash curl -X POST https://api.twikey.com/creditor/ibancheck \ -H 'authorization: **authorization**'\ -d 'name=Doortje Doorzon' \ -d 'iban=NL51ABNA0577013939' ``` ```php $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); ``` ```javascript 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); }); ``` ```java 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(); }; } ``` ```cs 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); } } ``` **Response** ```json { "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 | Name | Description | Required | Type | Max. | |---------|--------------------------------------------------------|----------|--------|------| | name | Name of the account holder | Yes | string | 70 | | iban | Account number | Yes | string | 34 | | flavour | `it` or `nl` integration to use, by default NL is used | No | string | 2 | ### Response Parameters | Name | Description | Type | |-------------------------------------|-----------------------------------------------------------------------------|---------| | iban.found | True if the IBAN is found in the registry | boolean | | iban.valid | True if the specified IBAN has a valid format | boolean | | iban.blackListed | True if the iban is blacklisted | boolean | | iban.nameMatching | True if the name matches the name of the account holder | boolean | | name.valid | The name **registered in the system** (not the one provided) is valid [*1]. | boolean | | name.suggestion | When the provided name doesn't match the account holders name or type [*2]. | string | | account.foreign | True if the account is Dutch | boolean | | account.countryCode | Country code of the account (ISO 2 characters) | string | | account.active | True if the account is active | boolean | | account.holderType | Type of account holder. [*3] | string | | account.holderResidenceMunicipality | City of the account holder | string | | account.holderOfficialName | Official name of the account holder | string | | account.holdersNumber | Number of account holders for this account | number | | account.holderJointAccount | True if it is a joint account | boolean | [*1] The name **registered in the system** (not the one provided) is valid * True (valid): The name is between 3 and 70 characters long. * False (invalid): if the name is shorter than 3/longer than 70 characters or if only first name is entered. [*2] When the provided name doesn't match the account holders name or type * When the provided name does not completely match the account holders name, a suggestion is returned. E.g.: in case of a mistype. * When the account belongs to an organization, the legal name of the organization is provided. [*3] Type of account holder. * NP: person * ORG: Organization * Unknown: account type is unknown ### IBAN Name Check Italy This checks that the IBAN and the VAT number or fiscal code match. ### Request Parameters Both `codfis`and `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`. | Name | Description | Required | Type | Max. | |---------|--------------------------------------------------------|----------|----------|------| | iban | Account number | Yes | string | 34 | | flavour | `it` or `nl` integration to use, by default NL is used | Yes | string | 2 | | codfis | Fiscal code related to the IBAN | No | String | | | vatno | VAT Number related to the IBAN | No | String | | ### Response Parameters | Name | Description | Type | |-------------|---------------------------------------------|---------| | unsupported | check is not supported for specified IBAN | boolean | | valid | IBAN check result is valid | boolean | | blacklisted | the given IBAN is blacklisted in our system | boolean | ### HTTP Response | Code | Description | |------|--------------------------------------------------------------------------------------------| | 200 | The request has succeeded | | 400 | User error if parameter is given but not valid (available in apierror header and response) | ### Error codes | Code | Description | |------------------------|-----------------------------------| | err_no_integration | No integration was configured | | err_invalid_name | The provided name is invalid | | err_err_invalid_iban | The provided account is invalid | | err_fail_integration | An error occurred on the system | | err_missing_attributes | missing required parameters | ## Accepted SEPA Characters Here you can find a list of the accepted SEPA characters that are accepted for transactional endpoints. **Response** ```json 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: * Go to your Twikey environment * Open the 'Settings' and select 'Api' * Click on 'Reset feed' 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... ```php 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¶m2=value%202¶m3=value%26` URL-decoding it would change %20 to a space and %26 to &, resulting in: `param1=value1¶m2=value 2¶m3=value&` Setup the different webhooks in your Twikey Dashboard under '[Settings: Api](/r/admin#/c/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 after 5 seconds. Like the scenario below, this briefly pauses delivery of other queued webhooks to your endpoint, but recovers quickly since the delay resets on each attempt. 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 for longer. 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 ```apache curl -H "apiToken=MyToken" http://localhost/callback \ -d type=contract \ -d reason=resumed \ -d mandateNumber=CORE01 \ -d name=Successful response (status=200):