Lühidalt: Sa saad luua Merit Aktiva müügiarve mis tahes sündmusest (trigger) nelja sõlmega n8n töövoo abil: Trigger, Code sõlm, mis ehitab JSON keha ja yyyyMMddHHmmss UTC ajatempli, Crypto sõlm, mis genereerib HMAC-SHA256 allkirja, ning HTTP Request sõlm, mis teeb POST päringu aadressile https://aktiva.merit.ee/api/v2/sendinvoice. Ainus konks, mis esimesel katsel 401 veakoodi tagastab: täpne baidijada, mida sa allkirjastad (apiId + timestamp + body), peab olema sama, mida sa saadad, seega serialiseeri JSON üks kord ja taaskasuta seda. Allpool on toodud konkreetne lahendus, allalaaditav töövoog ja selgitus, millal tasub pigem Make'i poole pöörduda.
Mida see Merit Aktiva + n8n töövoog tegelikult teeb?
See teeb ühte asja: n8n töövoog, mis muudab sissetuleva sündmuse (Stripe'i makse, vormi esitamine, webhook) Merit Aktivas sisestatud müügiarveks. Neli sõlme, ei mingit eraldi serverit ega varuloogikat. Trigger toob endaga kaasa arve andmed, n8n allkirjastab päringu ja Merit tagastab loodud arve GUID-i.
Kogu andmetoru koosneb neist neljast järjestikusest sõlmest:

Igal sõlmel on täpselt üks ülesanne. Ainus keeruline osa on kolmas sõlm, HMAC-SHA256 allkiri, ja n8n teeb seda oma Crypto sõlmega sisseehitatult, nii et sa ei pea kirjutama rida krüptograafiakoodi.
Millist nelja n8n sõlme on vaja?
Nelja, just sellises järjekorras: Trigger sõlm, Code sõlm, Crypto sõlm ja HTTP Request sõlm. Trigger võtab sündmuse vastu, Code sõlm ehitab JSON keha ja UTC ajatempli, Crypto sõlm allkirjastab apiId + timestamp + body ning HTTP Request sõlm saadab POST päringu, kus allkiri on päringu parameetrites (query string).
Rollid konkreetselt:
- Trigger sõlm (Webhook, Schedule või Stripe/Typeform trigger). See annab sulle toored arve väljad: kliendi nimi, registrikood, arve rida, summa.
- Code sõlm. See paneb kokku Meriti JSON keha, lisab
yyyyMMddHHmmssUTC ajatempli ja serialiseerib keha ühe korra stringiks. See string ongi see, mida sa nii allkirjastad kui ka saadad. - Crypto sõlm. Action
HMAC, algoritmSHA256, kodeeringbase64, saladuseks (secret) sinu API võti. See allkirjastab kokku pandud stringi. - HTTP Request sõlm. Meetod POST, URL
.../api/v2/sendinvoice, kolm päringu parameetrit (ApiId,timestamp,signature) ja toores JSON keha.
Sul ei ole vaja ühte HTTP sõlme autentimiseks ja teist päringu tegemiseks. Merit nõuab iga päringu eraldi allkirjastamist, seega allkirjastamine ja saatmine toimuvad samas kahes viimases sõlmes.
Kuidas ehitada JSON keha ja ajatempel Code sõlmes?
Ehita keha JavaScripti objektina, serialiseeri see ühe korra JSON.stringify abil ja loo ajatempel kujul yyyyMMddHHmmss UTC ajavööndis ilma eraldajateta. Salvesta serialiseeritud string elemendi külge, et järgmised kaks sõlme saaksid taaskasutada täpselt samu baite. Uuesti serialiseerimine allkirjastamise ja saatmise vahel on peamine 401 veakoodi põhjus.
Code sõlm, mis seda teeb:
const input = $json.body || $json;
const body = {
Customer: { Name: input.customerName, RegNo: input.regNo },
DocDate: input.docDate,
DueDate: input.dueDate,
InvoiceNo: input.invoiceNo,
InvoiceRow: [
{
Item: { Code: input.itemCode, Description: input.description },
Quantity: input.quantity,
Price: input.price,
TaxId: input.taxId
}
],
TaxAmount: [ { TaxId: input.taxId, Amount: input.taxAmount } ],
TotalAmount: input.totalAmount
};
const d = new Date();
const p = (n) => String(n).padStart(2, '0');
const timestamp = `${d.getUTCFullYear()}${p(d.getUTCMonth()+1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}`;
const bodyString = JSON.stringify(body);
const apiId = $vars.MERIT_API_ID;
const signBase = apiId + timestamp + bodyString;
return [{ json: { apiId, timestamp, bodyString, signBase, body } }];
Pane tähele, et TaxId on GUID, mitte protsent. Päri kehtiv 24% käibemaksu GUID üks kord gettaxes endpointist ja sööda see sisse triggeri kaudu. Kuna Eesti standardmäär tõusis 24% peale, teeb sissekirjutatud (hardcoded) vana käibemaksu GUID iga arve valesti.
Kuidas allkirjastada päring ilma krüptograafiakoodi kirjutamata?
Kasuta n8n-i sisseehitatud Crypto sõlme. Määra Action väärtuseks HMAC, Type väärtuseks SHA256, väärtuseks Code sõlmest saadud kokkupandud apiId + timestamp + body string, saladuseks (secret) oma API võti ja kodeeringuks base64. See toodab täpselt base64(HMAC-SHA256(apiKey, apiId + timestamp + body)), mis ongi see, mida Merit ootab.
Crypto sõlme seadistus väli-väljalt:
Action: HMAC
Type: SHA256
Value: ={{ $json.signBase }}
Property Name: signature
Secret: ={{ $vars.MERIT_API_KEY }}
Encoding: base64
Saladus on HMAC võti ja see ei lahku kunagi n8n-ist. Salvesta see n8n-i muutuja või mandaadina (credential), mitte otse sõlme sisse, et see ei satuks eksporditud töövoogudesse ega logidesse. Väärtus, mida sa allkirjastad, on string, mille Code sõlm juba ehitas, seega allkiri ja payload ei saa kunagi teineteisest erineda.
Milline näeb välja HTTP Request sõlm?
Meetod POST aadressile https://aktiva.merit.ee/api/v2/sendinvoice, kus ApiId, timestamp ja signature on päringu parameetrid ning serialiseeritud keha on toores application/json payload. Kriitilise tähtsusega on saata toores bodyString, mille sa juba ehitasid, mitte uus JSON objekt, et allkirjastatud baidid ja saadetud baidid klapiksid.
Siin on samaväärne päring toore allkirjastatud curl-ina, et saaksid täpselt näha, mida n8n väljastab, ja seda silumiseks väljaspool n8n-i reprodutseerida:
API_ID="REDACTED-API-ID"
API_KEY="REDACTED-API-KEY"
BASE="https://aktiva.merit.ee/api/v2"
BODY='{"Customer":{"Name":"Estonian Design OU","RegNo":"1234567"},"DocDate":"2026-07-01","DueDate":"2026-07-15","InvoiceNo":"2026-0042","InvoiceRow":[{"Item":{"Code":"CONS","Description":"Consulting, June 2026"},"Quantity":40,"Price":95.00,"TaxId":"REDACTED-TAX-GUID-24"}],"TaxAmount":[{"TaxId":"REDACTED-TAX-GUID-24","Amount":912.00}],"TotalAmount":3800.00}'
TS=$(date -u +%Y%m%d%H%M%S)
SIG=$(printf '%s' "${API_ID}${TS}${BODY}" \
| openssl dgst -sha256 -hmac "$API_KEY" -binary \
| base64)
curl -s "${BASE}/sendinvoice?ApiId=${API_ID}×tamp=${TS}&signature=${SIG}" \
-H "Content-Type: application/json" \
--data-raw "$BODY"
Määra n8n-is HTTP Request keha tüübiks raw / application/json ja seo see väärtusega ={{ $json.bodyString }}. Ära kasuta siin "JSON" keha ehitajat nime/väärtuse väljadega, sest n8n serialiseeriks selle uuesti ja sinu allkiri ei klapiks enam saadetud baitidega.
Mida Merit tagastab ja kuidas kinnitada, et see töötas?
Merit tagastab loodud dokumendi GUID-i ja numbri JSON-ina. Edukas sendinvoice vastus sisaldab välju InvoiceId (GUID), InvoiceNo ja juhul, kui Merit lõi kliendi lennult, ka CustomerId ja NewCustomer. Loe InvoiceId tagasi kasutajale või oma järgmisesse sõlme tõestusena, et müügiarve on sisestatud.
Redigeeritud vastus:
{
"InvoiceId": "b7c9e5a1-0000-4f22-8d3a-REDACTED",
"CustomerId": "8a1f3c2e-0000-4a11-9c7d-REDACTED",
"InvoiceNo": "2026-0042",
"NewCustomer": false
}
Kui saad hoopis 401 veakoodi, on see peaaegu alati üks kolmest asjast: ajatempel ei ole yyyyMMddHHmmss UTC ajavööndis, keha serialiseeriti allkirjastamise ja saatmise vahel uuesti või API võti kleebiti koos tühja reavahetusega lõpus. Allkirjasta vahetult enne saatmist, et kell ei nihkuks kunagi üle Meriti lubatud piiri.
Laadi töövoog alla
Impordi see ja ühenda oma trigger ning muutujad. See sisaldab kõiki nelja eelnevalt ühendatud sõlme, kus Code, Crypto ja HTTP sõlmed on seadistatud täpselt nii, nagu ülal kirjeldatud. Asenda Webhook oma tegeliku triggeriga ja määra MERIT_API_ID ning MERIT_API_KEY n8n-i muutujatena.
Kopeeri allolev töövoo JSON ja kleebi see n8n kanvaale (Ctrl/Cmd+V), et see importida:
{
"name": "Merit Aktiva - loo muumiarve triggeri pealt",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "merit-invoice",
"responseMode": "lastNode",
"options": {}
},
"id": "b1a1e100-0000-4000-9000-000000000001",
"name": "Trigger (Webhook)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-320,
320
],
"webhookId": "merit-invoice-demo"
},
{
"parameters": {
"jsCode": "// Ehita arve keha ja UTC ajatempel. Keha string, mille sa allkirjastad,\n// PEAB olema baithaaval sama, mille sa saadad. Serialiseeri korra siin.\nconst input = $json.body || $json;\n\nconst body = {\n Customer: { Name: input.customerName, RegNo: input.regNo },\n DocDate: input.docDate,\n DueDate: input.dueDate,\n InvoiceNo: input.invoiceNo,\n InvoiceRow: [\n {\n Item: { Code: input.itemCode, Description: input.description },\n Quantity: input.quantity,\n Price: input.price,\n TaxId: input.taxId\n }\n ],\n TaxAmount: [\n { TaxId: input.taxId, Amount: input.taxAmount }\n ],\n TotalAmount: input.totalAmount\n};\n\n// yyyyMMddHHmmss UTC-s, ilma eraldajateta\nconst d = new Date();\nconst p = (n) => String(n).padStart(2, '0');\nconst timestamp = `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}`;\n\n// Tapselt see string laheb allkirjastamisele: apiId + timestamp + bodyString\nconst bodyString = JSON.stringify(body);\nconst apiId = $vars.MERIT_API_ID;\nconst signBase = apiId + timestamp + bodyString;\n\nreturn [{ json: { apiId, timestamp, bodyString, signBase, body } }];"
},
"id": "b1a1e100-0000-4000-9000-000000000002",
"name": "Ehita keha + ajatempel",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-80,
320
]
},
{
"parameters": {
"action": "hmac",
"type": "SHA256",
"value": "={{ $json.signBase }}",
"dataPropertyName": "signature",
"secret": "={{ $vars.MERIT_API_KEY }}",
"encoding": "base64"
},
"id": "b1a1e100-0000-4000-9000-000000000003",
"name": "HMAC-SHA256 allkiri",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
180,
320
]
},
{
"parameters": {
"method": "POST",
"url": "https://aktiva.merit.ee/api/v2/sendinvoice",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "ApiId",
"value": "={{ $json.apiId }}"
},
{
"name": "timestamp",
"value": "={{ $json.timestamp }}"
},
{
"name": "signature",
"value": "={{ $json.signature }}"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ $json.bodyString }}",
"options": {}
},
"id": "b1a1e100-0000-4000-9000-000000000004",
"name": "Merit sendinvoice",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
320
]
}
],
"connections": {
"Trigger (Webhook)": {
"main": [
[
{
"node": "Ehita keha + ajatempel",
"type": "main",
"index": 0
}
]
]
},
"Ehita keha + ajatempel": {
"main": [
[
{
"node": "HMAC-SHA256 allkiri",
"type": "main",
"index": 0
}
]
]
},
"HMAC-SHA256 allkiri": {
"main": [
[
{
"node": "Merit sendinvoice",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"pinData": {}
}
Allkirjastamine asub ühes kohas, seega iga teine Meriti endpoint (getcustomers, gettaxes, getinvoices) taaskasutab sama Code ja Crypto paari. Kui soovid, et päringu allkirjastamise sisemust selgitataks eraldi, siis täielik Merit Aktiva API ja HMAC juhend käib allkirja baithaaval läbi.
Millal peaksid kasutama n8n-i ja millal Make'i?
Kasuta n8n-i, kui allkirjastamine nõuab kohandatud baite, ja Make'i, kui töövoog on lihtne ning soovid vähem koodi. Meriti HMAC skeem vajab Crypto sammu iseehitatud stringi peal, millega n8n-i Code ja Crypto sõlmed saavad puhtalt hakkama. Make suudab seda samuti, kuid selle HMAC-i käsitsemine on allkirjastatud payloadiga skeemi puhul tülikam, seega on n8n konkreetselt Meriti jaoks parem valik.
Vali n8n, kui: allkirjastatud string ei ole triviaalne (Meriti oma on), soovid hoida töövoo JSON-it versioonihalduses või majutad lahendust ise (self-hosting), et hoida finantsandmeid EL-is. Vali Make, kui: integratsioon on standardne konnektor ilma erilahendusena loodud allkirjastamiseta või kui seda haldav inimene eelistab puhtalt visuaalset ehitajat. Sügavam Make vs n8n vs AI-agent otsus on läbi töötatud eraldi artiklis. Kui sa ei ole veel kontot ja API kasutajat loonud, katab täielik Merit Aktiva seadistamise juhend Pro-paketi nõude ja näitab, kus API võti asub.
Kui müügiarved liiguvad puhtalt sisse, saadab sama kujuga töövoog need edasi e-arvetena; Eesti e-arvete juhend katab seda järgmist sammu.
Aluslabs ehitab just selliseid n8n töövoogusid Merit Aktiva peale, koos elava töölauaga, mis näitab tulemust reaalajas ilma iga kuu Excelit käsitsi kokku panemata - majandustarkvara automatiseerimine ja töölauad.
KKK
Kas n8n suudab luua Merit Aktiva arve ilma erikoodita? Peaaegu. Sul on vaja ühte väikest Code sõlme, et ehitada JSON keha ja UTC ajatempel ning serialiseerida keha ühe korra. Allkirjastamine ise ei vaja koodi: n8n-i sisseehitatud Crypto sõlm genereerib HMAC-SHA256 allkirja.
Miks minu n8n-i Meriti päring tagastab 401?
Tavaliselt serialiseeriti keha Crypto sõlme ja HTTP Request sõlme vahel uuesti, nii et allkirjastatud baidid ja saadetud baidid erinevad. Allkirjasta täpne bodyString string ja saada see sama string toore kehana (raw body). Kontrolli ka, et ajatempel oleks yyyyMMddHHmmss UTC ajavööndis.
Milline n8n sõlm allkirjastab HMAC-SHA256 päringu?
Crypto sõlm, mille Action on HMAC, Type on SHA256 ja Encoding on base64. Selle väärtus on kokkupandud apiId + timestamp + body string ja saladus (secret) on sinu Meriti API võti.
Kas HTTP Request sõlm peaks kasutama JSON keha või toorest (raw) keha?
Toorest. Seo keha juba serialiseeritud bodyString väärtusega ja määra sisutüübiks (content type) application/json. JSON nime/väärtuse ehitaja serialiseerib payload'i uuesti ja rikub allkirja.
Kas n8n või Make on Merit Aktiva jaoks parem? n8n, sest Meriti allkirjastatud payloadiga HMAC skeemi on puhtam ehitada n8n-i Code ja Crypto sõlmedega ning saad hoida töövoo JSON-it versioonihalduses. Vali Make ainult standardsete konnektorite jaoks, kus puudub erilahendusena loodud allkirjastamine.
Kuidas ma saan 24% käibemaksu GUID-i?
Tee üks kord päring gettaxes endpointi ja kaardista 24% standardmäär selle GUID-iga, seejärel anna see GUID sisse triggeri kaudu. Ära kunagi kirjuta protsenti või vana käibemaksu GUID-i koodi sisse (hardcode); standardmäär tõusis 24% peale ja vananenud koodid teevad iga arve valesti.