API
The Atlar API is a resource-based JSON API, following RESTful standards.
What is considered a Non-Breaking change
Endpoints, Fields and Request parameters will not be removed or changed without a change of version. However non-breaking changes to the API may be introduced without a version change. This includes new fields to JSON objects and new parameters to requests. In practice, this means that your clients need to handle unknown/unmapped fields and parameters. Introduction of new fields/parameters will not impact behavior of existing fields/parameters.
Lastly, there may exist fields that are not documented in this API reference. These fields are not considered official and above protection does not apply. Atlar may change those at any point, so usage thereof is at own risk.
Structure
The Atlar API is resource-oriented and follows REST/CRUD practices.
Method & Path | Description |
---|---|
GET /resource | Query among all of the same resource |
POST /resource | Create a new resource |
GET /resource/{id} | Get the identified resource |
PUT /resource/{id} | Update the identified resource (might be removed in favor for PATCH) |
PATCH /resource/{id} | Update specific fields of the identified resource (not implemented yet) |
DELETE /resource/{id} | Delete the identified resource |
No trailing slashes, and all paths have pluralized resource names
Authentication
Use Basic auth with the <ACCESS_KEY>
and <SECRET>
that you retrieve when creating programmatic access tokens.
Listing Accounts
using curl
would be done with:
curl 'https://api.atlar.com/v1/accounts' -u '<ACCESS_KEY>:<SECRET>'
Pagination
Pagination works the same on all Query endpoints. This includes GET endpoints of resource without specified identifier (e.g. GET /transactions
or GET /payments
).
The request has two common query parameters that can be used: limit
and token
. Limit, if included, must be between 1 and 500, if it is less or more than those values it will be coerced down or up to the respective limit. If limit is not included it will default to 100.
E.g.
-
GET /transactions?limit=20
will give 20 transactions (or up to 20 if there is less than 20 total transactions) -
GET /transactions?limit=20&token={TOKEN}
will give 20 transactions, starting from{TOKEN}
.
The response object will, no matter the resource, have the same shape. It will carry the four fields token
and limit
(indicating what limit and token that were used for the request), as well as nextToken
and items
. The string nextToken
, if non-empty, indicates what token one should use to continue pagination. It is empty if it is known to not exist any more items on the server at the point of the request. The list of items
carries the queried resources.
Never use the length of the items
list in comparison to the limit
to determine whether more pages exists. There may be (uncommon) cases where the number of items
returned, is lower than the limit that was asked for, even though more items exists on the server.
If you are using resource-specific filtering parameters when querying, those should stay the same throughout the pagination. If query parameters change, always restart pagination (by not setting
token
value in the request)
Response
{
"nextToken": "the token that should be used to get the next page, empty if no more",
"token": "the token used for this query",
"limit": 20,
"items": []
}
Idempotency
The Atlar API has built-in support for idempotency. Idempotency makes it possible to safely retry requests without risking to perform the same intended operation twice. This is in particular useful, or even needed, when an API request is disrupted, or in some way failing in transit, which renders the client without a response from the server. The problem in the scenario is that the client that didn't receive the response cannot know if the server actually performed the request or not. Which, without Idempotency, for a creation of a resource could lead to the multiple created resources if the request is retried. To solve this, Idempotency Keys are implemented and, if supplied, the same request can be retried multiple times, without the risk of multiple resources being created.
To make, an otherwise non-idempotent request, idempotent; supply the optional HTTP header Idempotency-Key
. The key is ignored for GET
and DELETE
requests and will expire automatically after 24h of first use. The uniqueness of a key is bound to the organization and endpoint (method and path). If a client would make a second request - with the same idempotency key - before the first request is done server-side, the server will respond with a 425 Too Early
status. This error code is always safe to retry.
If one wants to test how idempotency works on the Atlar API, an endpoint with the sole purpose of testing has been created on: POST /v1/idempotency-test
. It does not take any request body, but one can supply the two optional integer query-parameters status
and sleep
. The former will dictate which status the response will have, and the latter will make the server sleep for this amount of milliseconds. The server will, after any potential sleep, respond with the supplied idempotency key, a random uuid, as well as the requested status.
With this endpoint one can test how the API will behave when supplying an idempotency key.
Webhooks and events
Webhooks allow you to receive HTTP POST payloads whenever certain events happen. You can use the Webhook endpoints to add new or modify existing webhook configurations for your organization. The endpoints allow you to specify at which HTTPS URL you want to receive your webhook calls.
Events
Webhooks are based on our data model called events
which exist on multiple resources in the API. For example, the payment events endpoint lets you see everything that has happened to a given payment. Events for different resources (payments
, direct_debits
, etc.) share the same general structure. The table below shows which events that are possible for each resource.
API resource | Events |
---|---|
credit_transfers | CREATED, EDITED, STEP_APPROVED, FULLY_APPROVED, STEP_DEAPPROVED, REJECTED, SCHEDULED_FOR_SUBMISSION, DESCHEDULED_FROM_SUBMISSION, SENT_TO_BANK, INSTRUCTION_GENERATED, PAYMENT_ACCEPTED_BY_BANK, PAYMENT_ACCEPTED_DATE_CHANGED, CONDITIONALLY_ACCEPTED_AWAITING_SIGNATURE, ACCEPTED_INSUFFICIENT_FUNDS_RETRYING, PENDING_AT_BANK, EXECUTED_BY_BANK, RECONCILED, RETURNED, ATTACHED_TRANSACTION, FAILED, OPERATOR_FORCE_UPDATE |
direct_debits | CREATED, EDITED, STEP_APPROVED, FULLY_APPROVED, STEP_DEAPPROVED, REJECTED, SCHEDULED_FOR_SUBMISSION, DESCHEDULED_FROM_SUBMISSION, SENT_TO_BANK, INSTRUCTION_GENERATED, PAYMENT_ACCEPTED_BY_BANK, PAYMENT_ACCEPTED_DATE_CHANGED, CONDITIONALLY_ACCEPTED_AWAITING_SIGNATURE, ACCEPTED_INSUFFICIENT_FUNDS_RETRYING, PENDING_AT_BANK, EXECUTED_BY_BANK, RECONCILED, RETURNED, ATTACHED_TRANSACTION, FAILED, OPERATOR_FORCE_UPDATE |
expected_transactions | CREATED, RECONCILED, UNRECONCILED, ARCHIVED, UNARCHIVED, DELETED |
mandates | CREATED, SCHEDULED_FOR_SUBMISSION, DESCHEDULED_FROM_SUBMISSION, SCHEDULED_FOR_CANCELLATION, DESCHEDULED_FROM_CANCELLATION, FAILED, SENT_TO_BANK, ACCEPTED_BY_BANK, FIRST_COLLECTION_RECONCILED, CANCELLED, CANCELLED_BY_COUNTERPARTY, OPERATOR_FORCE_UPDATE |
payments | CREATED, EDITED, STEP_APPROVED, FULLY_APPROVED, STEP_DEAPPROVED, REJECTED, SCHEDULED_FOR_SUBMISSION, DESCHEDULED_FROM_SUBMISSION, SENT_TO_BANK, INSTRUCTION_GENERATED, PAYMENT_ACCEPTED_BY_BANK, PAYMENT_ACCEPTED_DATE_CHANGED, CONDITIONALLY_ACCEPTED_AWAITING_SIGNATURE, ACCEPTED_INSUFFICIENT_FUNDS_RETRYING, PENDING_AT_BANK, EXECUTED_BY_BANK, RECONCILED, RETURNED, ATTACHED_TRANSACTION, FAILED, OPERATOR_FORCE_UPDATE |
pending_transactions | CREATED, UPDATED, DELETED |
transactions | CREATED, RECONCILED, UNRECONCILED, DELETED, EDITED |
Note1: when creating a webhook you must provide an inclusive filter
to specify which events you want to receive.
Note2: the event DELETED
, might not be an event that can occur in a regular business process. However, in some cases, maybe because of an issue on either Bank or Atlar side, there may be need for data cleanup, and in that case DELETED event will be sent.
Note3: the event OPERATOR_FORCE_UPDATE
only occurs when an operator from Atlar has manually forced an update of the resource. This is not a part of the regular business process but may occur when there is an issue on either Bank or Atlar side and there is a need to manually update the status of a resource.
Webhook payload
The webhook payload contains:
- Information about the event that occurred (
event
andresource
). - The underlying resource that was affected by the event (
entity
).
Below is an example of a direct debit that was returned by the bank due to a refund.
{
"resource": "direct_debits",
"event": {
"organizationId": "208086fb-f8c3-402c-bb6d-820aa57f782d",
"entityId": "1d0fe618-3f76-407a-9635-fbebc3783256",
"id": 5,
"timestamp": "2022-12-13T14:00:00Z",
"name": "RETURNED",
"originator": "AVANTIR",
"message": "RefundRequestByEndCustomer — Return of funds requested by end customer",
"details": {
"returnReason": "MD06",
"transactionId": "e006e543-e60a-449e-9444-f343e02d6343"
}
},
"entity": "{...}"
}
Webhook retries
Webhook requests are retried should they fail or if your server responds with anything but 2xx
HTTP statuses.
These systematic retries will happen 3 times every 15 minutes during 12 hours. If your system is down for more than 12 hours, or you lost your webhook information in some other way, you can retrieve the lost information on the event endpoints on the API to get all historic events for a specific resource.
Webhook security
We will send a few HTTP headers with each webhook request. Below is a summary of what headers we send and why.
- Signature
- Header:
Webhook-Signature
(case-insensitive) - Format: HMAC-SHA256
- Example:
e71354d023f850abcb1bfd884de2874bc973dcbb4b44acac5fe4ca89e28d12ed
(multiple signatures will be separated by a comma,
) - Purpose: Makes it possible for your app to verify that the webhook was sent from Atlar (authenticity), and that the message hasn't been altered (integrity).
- Header:
- Timestamp
- Header:
Webhook-Request-Timestamp
(case-insensitive) - Format: RFC3339 (UTC, nano)
- Example:
2022-10-11T10:13:14.000000015Z
- Purpose: Used to mitigate replay attacks. The value is set to the time that the HTTP request was made.
- Header:
Webhook signature and timestamp verification
We will generate a unique webhook key for each and one of your endpoints, which you will see once after creating it via the Webhook API. Store the key as a secret in your application. We use this key to create the HMAC-SHA256 signature, and you can use it to validate our signature.
In your application, write a function that executes the following steps:
- Decode the key into bytes. The key is a byte array with standard base64 encoding.
- Create the signature payload by concatenating:
- The webhook request body as a JSON string.
- A period
.
. - The timestamp from the header (as a string).
- Compute a HMAC (Hash-based Message Authentication Code) using the SHA256 hash function.
- Encode the HMAC bytes into a hexadecimal string so it can be compared with the value in the signature header.
- Compare the two values using a constant-time string comparison (not a plain
==
). This helps to mitigate timing attacks. The HMAC library you're using might have an equality function for this purpose. - Compare the time difference between the request timestamp and the current time (UTC) and check that the message isn't too old. You can choose a certain time tolerance such as 5min. This helps to prevent replay attacks, where an attacker would re-send an old request.
Note that if you have multiple keys for one endpoint (during key rotation), there will be multiple signatures separated by a comma ,
.
Code example (Go)
func signatureIsValid(sigHeader, tsHeader, payload, base64Key string) (bool, error) {
key, err := base64.StdEncoding.DecodeString(base64Key)
if err != nil {
return false, fmt.Errorf("failed to decode key, %w", err)
}
sig, err := hex.DecodeString(sigHeader)
if err != nil {
return false, nil
}
mac := hmac.New(sha256.New, key)
mac.Write([]byte(payload + "." + tsHeader))
return hmac.Equal(mac.Sum(nil), sig), nil
}
Example values
Webhook-Signature: fe8f799f90ecfe57ce9ae19d3429be0ca3c0e5ae336fdf3e08dd1f7b60a15a6f
Webhook-Request-Timestamp: 2022-10-06T07:26:57.237369365Z
Key (base64): agj+xWKk3gqkP+SsCsljkjbDth7bxguqVMRd4K3wm1I=
Body: {"resource":"payments","event":{"organizationId":"1f91e001-9295-46b6-9438-ef6f0fed18fc","entityId":"422a164c-4548-11ed-8d31-0a58a9feac02","id":0,"timestamp":"2022-10-06T07:26:56.837022728Z","name":"CREATED","originator":"90b51164-d782-4238-a0bf-c1ff66a8a83e","message":"","details":{"request":"ewogICAgImFtb3VudCI6IHsKICAgICAgICAiY3VycmVuY3kiOiAiU0VLIiwKICAgICAgICAidmFsdWUiOiA1MDAwCiAgICB9LAogICAgInNvdXJjZUFjY291bnRJZCI6ICJjZjAxMGY5MC04M2UyLTQyMGQtYjE2ZS1lYjY4ZGJmZDcwNDciLAogICAgImRlc3RpbmF0aW9uRXh0ZXJuYWxBY2NvdW50SWQiOiAiZTgwZGU1ZTAtMWIwNS00OTc0LThjNzMtMDg0MDI3MTg1YzdmIiwKICAgICJkYXRlIjogIjIwMjItMTAtMTAiLAogICAgInJlbWl0dGFuY2VJbmZvcm1hdGlvbiI6IHsKICAgICAgICAidHlwZSI6ICJVTlNUUlVDVFVSRUQiLAogICAgICAgICJ2YWx1ZSI6ICJUZXN0aW5nIHdlYmhvb2tzIgogICAgfSwKICAgICJwYXltZW50U2NoZW1lVHlwZSI6ICJTQ1QiCn0="}},"entity":{"amount":{"currency":"SEK","value":5000},"approvalChain":{"approvalSteps":[{"id":"b698ab4e-efec-4c97-9133-88904022cd1b","requiredRole":{"id":"4ce82d1a-0ebf-46e7-96ba-7664cd48e4af","name":"Owner","owner":true},"status":"PENDING","updated":"0001-01-01T00:00:00Z"}],"triggeredApprovalChainId":"54e2c37d-fa95-4d5b-bbcf-b99c8ff8676f"},"attachedTransactions":[],"created":"2022-10-06T07:26:56.836974Z","date":"2022-10-10","destinationCounterparty":{"id":"7af0ea0b-7366-42aa-ad83-49e93e41ce5d","identifiers":[],"name":"Company Inc.","partyType":""},"destinationExternalAccount":{"bank":{"bic":"12345678900","id":"","name":""},"counterpartyId":"7af0ea0b-7366-42aa-ad83-49e93e41ce5d","id":"e80de5e0-1b05-4974-8c73-084027185c7f","identifiers":[{"holderName":"Test","market":"DE","number":"DE66500105172794778236","type":"IBAN"}],"organizationId":"1f91e001-9295-46b6-9438-ef6f0fed18fc"},"externalMetadata":null,"id":"422a164c-4548-11ed-8d31-0a58a9feac02","organizationId":"1f91e001-9295-46b6-9438-ef6f0fed18fc","paymentScheme":{"displayName":"Sepa Credit Transfer","type":"SCT"},"paymentSchemeType":"SCT","reconciliation":{"status":""},"remittanceInformation":{"type":"UNSTRUCTURED","value":"Testing webhooks"},"sourceAccount":{"affiliation":{"id":"49b72264-fae5-4720-89cf-f0d6fd5f727e","name":"My bank"},"bank":{"bic":"ATLRSESSXXX","id":"3dde93bf-49d1-44e4-a288-4bf0d4e31574","name":"Testbank"},"id":"cf010f90-83e2-420d-b16e-eb68dbfd7047","identifiers":[{"holderName":"Test Testsson","market":"DE","number":"DE77500105179251553356","type":"IBAN"}],"name":"My EUR account","owner":{"name":""}},"status":"CREATED"}}
Webhook idempotency and ordering of events
It's important that your webhook handler is idempotent. This means that two identical webhook calls will give the same result as one single call. Due to our behavior with retries, we can't guarantee that we won't send a webhook twice. Additionally, an attacker could resend a webhook in a replay attack. One way of achieving idempotency is to rely on a combination of the event.id
(example: 3
) and the entity.id
(example: e4588d47-8d7e-48a0-a6f5-6960a19b45b3
). These IDs will stay the same between retries, and combined they are unique for the given event that occurred. You can extract the IDs from the webhook payload and check if you have processed it before by storing previously seen IDs somewhere. Atlar will never send webhooks that are older than 120 hours, meaning you won't have to store the IDs for longer than that.
In addition to idempotency, it's important that your handler isn't strictly dependent on the ordering of the webhook calls. In most cases, webhooks will naturally be delivered in order, but this is no guarantee. To know the end state of the entity for the webhook, you can make use of the event.timestamp
and entity
fields. The entity
always contains the entity as it looked like right after an event occurred. For example, if an event occurred that resulted in a status change of a payment, the entity
will contain the updated payment with the new status.
Webhook key rotation
Webhook keys can be rotated as many times as you want. If your webhook key is compromised, you can use the rotation functionality to generate a new one. Use the /v1/webhooks/{id}/keys
endpoints to generate or delete keys. Since you can have between 1 and 2 keys at once, rotation can happen without any application downtime.
Going live with webhooks
Before you can start receiving webhook calls from Atlar, the webhook configuration must be verified
. You can contact us at [email protected] for this purpose. Note that modifying the url
of a webhook will set verified
to false.
Errors
In general the Atlar API uses the following standard error codes
Code | Description | |
---|---|---|
400 | Bad Request | The request is invalid |
401 | Unauthorized | The authentication is wrong or non-existent |
403 | Forbidden | The used API key does not have permission to view/modify the resource |
404 | Not Found | The identified resource could not be found |
405 | Method Not Allowed | The accessed method is not valid |
409 | Conflict | The identified resource already exists. |
410 | Gone | The identified resource does not exist anymore |
425 | Too Early | If same idempotency key is used for multiple requests, but the server is not done with previous request. Client is supposed to retry this status. See more about Idempotency above. |
429 | Too Many Requests | The amount of requests made for the past minute exceeds our rate limit |
500 | Internal Server Error | Some unexpected server-side issue has occurred |
503 | Service Unavailable | Temporarily offline for maintenance |
Request IDs
Each API request will be assigned a unique request identifier. This identifier will be included in the API response in the HTTP header request-id
. If you need to contact us about a particular request, please provide the request identifier.
Updated 2 months ago