Talking Peppol made easy

EasyUBL Api is the programmer's tool. Here you will find the functions that work with the ERP system as the active party. EasyUBL responds back with webhooks. In many cases, it will be appropriate to build a middleware application that responds to the webhooks EasyUBL sends.
If you do not have a tenant, you can work directly in the sandbox. You should not create "real" companies in the sandbox, as all other users can see your data. It will be announced on the Peppol network that the companies you create can receive UBL documents and these will be sent to EasyUBL's sandbox for everyone to see. The sandbox contains two companies registered on the Peppol network. You can try to send documents between these companies.

If you want to work with your own data, you must contact EasyUBL to receive a Tenant key. If you create a company with this key, you are on the peppol network immediately, and you will immediately start receiving documents that you can see in the bookkeepers page.



On the bookkeepers page you can see the companies you create and the invoices you receive. This way you can work directly in the API and see the result.

The credentials for the sandbox is
User: SandboxAdmin
Password: SandboxAdmin!
control code: 0000

  

Along with your own tenant key, you will receive login credentials for this tenant's administrator.

Before you can send/receive, you must create a company

If the company is not registered in advance by another intermediary, the registration will be done immediately and you are ready to send/receive.
If the company is registered by another intermediary, the function will announce this. You must then ask this company to cancel the registration.

As soon as you have completed the creation, the company will receive documents. You can immediately access the documents you receive in "The bookkeeper's page".
Please note that you must use the established company's contact information (email, SMS) to access the documents.

As the company address is used as a sender address (Supplier) in the document, you must continuously maintain the address Information.
Creation and maintenance is handled by a single function in EasyUBL “API/Company/Update”. The company is identified in EasyUBL with a guid (key). The first time you call "Company/Update" you deliver an empty guid. This tells EasyUBL that you want to create a new company. Company/Update will return a guid, that you store in the local database. This guid will be used for future maintenance.

You call Company/Update every time a company address is changed. If it is difficult to incorporate this call into your application, the maintenance can be carried out by a small console application that replicates with a time interval.
API/Company/AddUpdateCompany
HttpClient myClient = new HttpClient();

myClient.DefaultRequestHeaders.Add("Authorization", ApiKey);
string myUrl = String.Concat("https://EasyUBL.net/api/Company/Update/", CompanyId);
string myCompanyJson = JsonConvert.SerializeObject(myCompany);
var stringContent = new StringContent(myCompanyJson, UnicodeEncoding.UTF8, "application/json");


var response = await myClient.PostAsync(myUrl, stringContent);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
string sub = (string)JsonConvert.DeserializeObject(responseContent);
CompanyId = Guid.Parse(sub);
// save the company guid in your application
}
-
public class companyEasyUBL
{
public string name { get; set; }
public string cvr { get; set; }
public string currency { get; set; }
public string country { get; set; }
public string? webhookUrl { get; set; }
public addEndpoint defaultEndpoint { get; set; }
public addAddress defaultAddress { get; set; }
public addContact defaultContact { get; set; }
public addPayment payment { get; set; }
public bool? DoNotReceiveUBL { get; set; } = false;
}

public class addEndpoint
{
public string endpointType { get; set; }
public string endpointIdentifier { get; set; }
}

public class addAddress
{
public string? name { get; set; }
public string? streetName { get; set; }
public string? additionalStreetName { get; set; }
public string? buildingNumber { get; set; }
public string? inhouseMail { get; set; }
public string? department { get; set; }
public string? attensionName { get; set; }
public string? cityName { get; set; }
public string? postalCode { get; set; }
public string? countrySubentity { get; set; }
public string? countryCode { get; set; }
}

public class addContact
{
public string roleCode { get; set; }
public string name { get; set; }
public string email { get; set; }
public string sms { get; set; }
}

public class addPayment
{
public string bankname { get; set; }
public string bankRegNo { get; set; }
public string bankAccount { get; set; }
public string bic { get; set; }
public string iban { get; set; }
public string CreditorIdentifier { get; set; }
}

The preferred way to send an invoice

Sending invoices and receiving notifications should be the easy way to handle an invoice. The possibilities depend entirely on the extent to which it is possible to make changes to the ERP system. The integration with EasyUBL is the easy part. Don't underestimate the importance of handling notifications.
Invoices are sent with the feature /api/ SendDocuments/ Invoice/ {companyId}

The function demands "companyId" as a parameter to identify the sender company address (the supplier). This ensures that no one sends invoices on your behalf through EasyUBL. This information is found in your database after calling AddUpdate (create companies).

The function SendDocuments/Invoice" sends the invoice and returns the complete UBL document.

HttpClient myClient = new HttpClient();
myClient.DefaultRequestHeaders.Add("Authorization",myTenantKey);
string myUrl = String.Concat("https:// EasyUBL.net/api /SendDocuments/ Invoice/", companyId);

string myInvoiceJson = JsonConvert.SerializeObject(myInvoice);
var stringContent = new StringContent(myInvoiceJson, UnicodeEncoding.UTF8, "application/json");

var response = await myClient.PostAsync(myUrl, stringContent);
response.EnsureSuccessStatusCode();
myXmlDocument = await response.Content.ReadAsStringAsync();
You should archive the complete UBL document in the database. This is the legal document the recipient receives. In the event of disputes, the complete UBL document is what must be referred to. Human readable versions formed on the fly like PDF, HTML etc. must be considered "copies with special features" like being human readable, but they are not the legal document. That is solely the actual complete UBL document.
Some recipients have special requirements for the content, that must be agreed with the invoice issuing company. For example, this could be a requisition number, specific contact persons, accounting codes, cost centers etc.
If the recipient has an elaborate system, a message will be returned, if any of these informations are missing. If the recipient behaves correctly, this message will indicate, where in the UBL document this information must be placed.
This means that you will be notified very quickly after sending a document, and you can then add any missing information, if that's the case, and resend it right away, not having to hunt down someone at the sender, who knows where the information should be found in the document.
Furthermore, the UBL document is the legal document. In the event of disputes, this is what must be referred to. Human readable versions can be considered copies with special abilities that are formed on the fly.
The document may be built in this way. This is only an example to illustrate the scope. Data is read from an SQL server and placed in the json document.
SqlDataReader myr = Comm.ExecuteReader();
if (myr.Read())
{ int AddressID = (int)myr["So_addressID"];
if (AddressID == 0) errCode = 1;
int CreInvFactor = (int)myr["CreInvFactor"];
if (CreInvFactor == -1) myI.invoiceCreditnote = "Cre"; else myI.invoiceCreditnote = "Inv";
myI.id = new(myr["InvoiceNo"].ToString());
myI.documentCurrencyCode = myr["Currency"].ToString().ToUpper();
myI.issueDate = (myr["InvDate"].Equals(DBNull.Value) ? DateTime.Today : (DateTime)myr["InvDate"]);
myI.dueDate = (myr["PayDate"].Equals(DBNull.Value) ? DateTime.Today : (DateTime)myr["PayDate"]);
myI.accountingCost = myr["AccountingCost"].ToString();
myI.buyerReference = myr["requisition"].ToString();
long OrderNo;
long.TryParse(myr["OrderNo"].ToString(), out OrderNo);
myI.salesOrderID = OrderNo.ToString();
myI.note = string.Concat(myr["text_1"], " ", myr["text_2"]).Trim();
if (myr["ShipDate"].Equals(DBNull.Value)) myI.deliveryDate = myI.issueDate; else myI.deliveryDate = (DateTime)myr["ShipDate"];
int thisContId = myr["ContID"] == DBNull.Value ? 0 : (int)myr["ContID"];
myI.accountingCustomerParty = GetPartyInfo((int)myr["So_addressID"]);
myI.accountingCustomerParty.Contact = GetContactInfo((int)myr["So_addressID"], thisContId);
if (string.IsNullOrEmpty(myI.accountingCustomerParty.Contact.name))
{
myI.accountingCustomerParty.Contact.name = myI.accountingCustomerParty.name;
}
myI.totalAmount = (decimal)myr["amount"];
myI.accountingCustomerParty.postalAddress = GetPostalAddress((int)myr["Sh_addressID"]);
if ((int)myr["Sh_addressID"] != 0) myI.deliverAddress = GetPostalAddress((int)myr["Sh_addressID"]);
myI.invoiceLines = GetLines(saleID);
}
return myI;
It is a traditional mapping of the fields found in the local database for the JSON document describing the invoice. Enjoy the fact that it is significantly easier to map for this document than to the underlying XML document.

Feel free to use the source text for this application. If you turn to Easyubl support, we will be happy to help with advice and guidance.

A real-time and seamless integration

To receive a webhook, you must have an api that can service all of your tenant's companies. EasyUBL webhook calls a url, that you provide when creating your tenant
If you create multiple Webhook recipients, EasyUBL will send the same notifications to all of them. For example, you can send notifications about address creations to your ERP system, your webshop, your CRM system, etc. EasyUBL sends webhooks all the time. You don't need to respond to them. We will resend a notification a certain number of times before giving up.

We send the notifications we receive from the UBL network to you as WebHooks. In addition, we send events from the EasyUBL applications.

It could be: https://myBasicURL/api/ tools/ UBLNotifications

The webhook call to your service will contain a small json document:

{
"companyId": "c9b89d84-cdb6-4207-8a99-23c7ffb04577",
"endpointId": "df4967d4-7e4d-4f23-9653-b8bd1b8719b2",
"ublDocumentId": "22323456",
"documentStatusCode" : "5110",
"documentType": "110",
"actionCode": "2",
"errorCode": "-1",
"base64EncodedMessage": "Not sent",
"externalIdentifier": "5053387"
"documentXmlBase64Content"
}


CompanyId will make it possible to find the company in your application. This is the key that was returned, when you created the company. You probably don't use EndpointId for anything.
ublDocumentID is an internal ID in the system, that makes it possible to backtrace the document. You don't use it.
externalIdentifier will contain your invoice number/order number etc. depending on the document type.


We currently support document types 100 (order), 110 (Invoice), 120 (credit note) and Essy100 (Financial documents and goods transactions). "base64MEssage" and "documentBase64" will contain information related to the document type.

This is an example of what a webhook might look like.
[Route("UBLNotifications")]
[HttpPost]
public int UBLNotifications(Notification ublNotification)
{
  if (ublNotification.companyId == Guid.Empty) throw new ArgumentException("Missing companyId");
  DBUser.ConnectionGetByEasyUBLKey(ublNotification.companyId);
  string DocumentType = ublNotification.documentType;
  if (DocumentType == "110" || DocumentType == "120") // invoice/ creditnote
  {
   NotificationService myUBL = new NotificationService(DBUser);
   if (ublNotification.documentStatusCode < 5200)
   {
   String message = myUBL.UpdateInvoice(ublNotification);
   }
  }
  if (DocumentType == "100")
  {
   NotificationOrderService myUBL = new NotificationOrderService(DBUser);
   if (ublNotification.documentStatusCode < 5200)
   {
    String message = myUBL.UpdateOrder(ublNotification);
   }
  }
  if (DocumentType == "EasyUBLDocumentBooked")
  {
   NotificationVoucherService myUBL = new NotificationVoucherService(DBUser);
   String message = myUBL.ProcessVoucher(ublNotification);
   }
  if (DocumentType == "EasyUblProductChanged")
 {
  EasyUblProductService myUBL = new EasyUblProductService(DBUser);
  String message = myUBL.OnProductChanged((ublNotification));
 }
 if (DocumentType == "EasyUblDocumentReceived")
 {
  NotificationVoucherService myUBL = new NotificationVoucherService(DBUser);
  String message = myUBL.OnDocumentReceived((ublNotification));
 }
  return 0;
}
A document with a document type of 5200 and above is for receipt. This is handled by the "Bookkeepers Page". Notifications below 5200 contain information about the document's shipment. This information can be used to update directly in the ERP system.

A document with DocumentType "EasyUBLDocumentBooked" is a document that must be saved in the ERP system. The call is made by Bookkeepers Page when the paper flyer is clicked that transfers the document to the journal. The document that is delivered is similar to that returned by the API called "NextTransaction". The call makes it possible to transfer vouchers to the ERP system in real time. the field "documentXmlBase64Content" will contain at json document:

EasyUblDocumentReceived contains the same document as "EasyUBLDocumentBooked" but with less information. It is the result of the first attempt to interpret the document. The call comes when the document arrives at EasyUBL. The document contains, among other things, the creditor's address, and can be used to create the creditor in the ERP system. It will be convenient that the creditor exists when the invoice arrives.

EasyUblProductChanged contains a document with product information. The call is sent every time an item is changed in EasyUBL and is used to maintain products in the ERP system. EasyUBL will attempt to map suppliers' products to the buyer's and, for example, convert units and packages if the necessary information is found in the UBL document.

We talk to everyone

Webhooks from EasyUBL can either be sent to a central API, or to a locally installed middleware belonging to a single company. This middleware must contain a single API call that receives webhooks from EasyUBL. If it works directly against the locally installed database, the system will work in real time.

If the machine the database is installed on is visible from the web, and you have the ability to write to the database, we can communicate this way. If you wish, we can send you an example written in C# of an API that receives webhooks from EsyUBL. You then write the part that communicates with the database.

The alternative methods are not described here, but you are welcome to contact EasyUBL if you need alternatives. We can usually find a solution.

Notifications

Numeric document types are the standard UBL document type. These arrive when the document is accepted. The field "documentXmlBase64Content" contains the xml document as we receive it. We do not sort the documents so you will receive the documents you are registered to receive.

Documenttype 100: Order, 110: Invoice, 120 Creditnote.

If "documentStatusCode" contains a value less than 5200, it is a notification that provides a status for a sent document. The status message is in "base64EncodedMessage". The function enables the sender to continuously follow the document's path to the recipient and finally receive a receipt for receipt.

Send documents

If "documentStatusCode" contains the value less than 5200, it is a notification that a sent document is on its way out. The "base64EncodedMessage"" field will contain a description of the document's status.

Before the document is sendt, thise codes may occur:

100: Waiting for robot.
This means that the document has been queued but not yet sent.

110: Sender company not found. There may be missing information on the company (endpoint, company no, country code, currency, e-mail etc.)
This may be because EasyUBL cannot create the company because information is missing.

111: The recipient: xxxxx is not registered to receive.
The recipient must be registered to receive UBL documents. Otherwise there is no point in trying to send the document.

While the document is in transit, these codes are received. This list may not be complete as changes can be made without EasyUBL needing to know.

5101: Document not sent
The reason is described in the error message.

5110: Pending
The document is awaiting validation and sending.

5120: Pending after validation
The document has been validated. If the validation had failed, a code 5101 would have been sent instead with the validation result in the error message.

5130: Sending
The document is located somewhere between the sender and the recipient.

5140: Send
This means that the recipient's system has acknowledged receipt. This does not necessarily mean that the recipient has received the document themselves, but that the responsibility for delivery lies with the recipient's system.

5150: Received
The recipient's own ERP system has accepted the receipt. They have not necessarily accepted the document but they have received it. This status is only achieved if the recipient's own approval flow supports this feature.

5160: Confirmed
The recipient has accepted the document. This status is only achieved if the recipient's own approval flow supports this feature.

5170: Rejected
The recipient has rejected the document. This status is only achieved if the recipient's own approval flow supports this feature.

Receive documents

If "documentStatusCode" contains the value 5210, it is a notification that a document has arrived. The document is found in the "documentXmlBase64Content" field. All document types are received in this way.
The "ublDocumentId" field is unique for each document. Use this to avoid receiving the same document multiple times.

Processed documents

The documents can be processed in the EasyUBL applications. The result is received in a webhook.

EasyUBLDocumentBooked: Is fired when you press post in the Bookkeeper´s page application. The "documentXmlBase64Content" field contains the finished document that must be submitted to the ERP system's draft along with the original xml document and the human-readable formats html and pdf.

EasyUblDocumentReceived: Is fired when EasyUBL processes a received document. The "documentXmlBase64Content" field contains an incomplete document, which can be used, for example, to create a new vendor in the ERP system. This will make it easier to process new vendors if they are already known in the ERP system when the invoice is requested. You can also use the function to maintain vendor addresses.

EasyUblOrderReceived: Is fired when an order is completed in the application Consignor´s page approves an order for receipt. In Consignor´s page, an order can be adjusted for product numbers, units, delivery times, etc. The adjusted order is expected to be created in the ERP system.

EasyUblProductChanged: Is fired when a product is changed in the Bookkeeper´s page and Consignor´s page. This opens the possibility to maintain product information manually in EasyUBL, import catalog information or perhaps inherit information from a supplier.
An unhandled error has occurred. Reload 🗙