Who this part is for
Exclusively for registered intermediaries
The partner notification webhook, client management via the API (release, pause-sending, resume-sending), the choice of billing model and termination from the application are available exclusively to the account of a registered intermediary (sprostredkovateľ). Technically they are bound to the partner record of the account under which your clients’ companies are created after the selection on the Financial Administration portal; an ordinary account gets them back as unavailable (not_a_reseller).
What an ordinary multi-tenant account has
- One API token for all companies of the account (X-Peppol-Participant-Id header in SAPI-SK).
- A notification webhook configured per company (GET/PUT /api/v1/companies/{id}/notifications) with the same X-Verteco-Signature signature.
- Deregistration of its own company (POST /api/v1/companies/{id}/deregister) and per-company usage (GET /api/v1/companies/usage).
- Companies are always managed by their owner, not by a partner: unilateral client release and the sending brake do not exist in the ordinary model.
How to become an intermediary
Fill in the application on the Become a digital postman page; we sign the Financial Administration form and file it for you. The fee is 99 € per year excluding VAT. Once you are published in the Financial Administration selection, clients select you under your brand, their companies are created under your partner account automatically and registered in the central SMP unless another provider holds their record (then you receive the company.smp_registered_elsewhere event and the migration-code procedure). In the test environment the intermediary registration is free of charge and you get a test partner endpoint right away.
Guide for SaaS / platforms
If you run an invoicing app, an ERP or a platform and want to connect several of your customers (tenants) to Peppol through us, you integrate once and serve N companies. The model is a proxy: your backend holds one API token (vpt_…) server-side only (never in the browser), and each of your tenants = one company with us (one token → N companies). The extended public guide with sample code, webhook signatures and a go-live checklist: peppol.verteco.digital/saas.
Security assurance for your clients: we are a certified provider of the Slovak Financial Administration (EFSK000031) with our own access point; ISO/IEC 27001:2022 certification is under way with the accredited body SKQS, s. r. o., Žilina (SNAS), selected in September 2026, certificate planned by 1 July 2027; OpenPeppol requires it from every provider from 1 October 2027, so no provider has to hold it yet. Contracts (terms, DPA, SLA, NDA) are published in full at /zmluvy, availability at /status.
- 1
One token, server-side
Create an API token and keep it in a secured backend environment. All calls are made by your server (Bearer), not by the customer's browser. - 2
Tenant onboarding = creating a company
For each customer, callPOST /companieswith their company registration number (IČO) / VAT ID (IČ DPH); it returns{ id, status: "pending_verification" }; store theidwith the tenant (for details see Companies).bashcurl -X POST https://peppol.verteco.digital/api/v1/companies -H 'Authorization: Bearer vpt_8f2a…' \ -H 'Content-Type: application/json' \ -d '{"ico":"53412834","dic":"SK2121358349","legalName":"Firma s.r.o.", "street":"Príkladná 12","postalCode":"010 01","city":"Žilina","iban":"SK…"}' - 3
Activation in Peppol (a step the customer takes with the state)
pending_verification ≠ live in Peppol. For a company to receive, the customer activates it by selecting a provider on the portal of the Financial Administration of the Slovak Republic (Finančná správa, FS), logging in with eID or the FS portal credentials. You are a registered intermediary: the customer selects your brand through your selection link (fieldvpdsUrlinGET /api/v1/resellers/me), not Verteco directly, because a direct selection of Verteco moves the company out of your program (eventcompany.released_to_direct). FS sends the selection to your webhook, you forward it to us (chapter FS webhook), and we activate the company, register it in the SMP and send youcompany.activated. You are not a registered intermediary: the customer selects Verteco as the provider and the rest is the same. The verified selection also authorises the company to send. If a direct selection of Verteco did not reach us, the fallback is Sending verification (POST /companies/{id}/verification). If the customer already had another provider, the company is activated after the selection but the SMP record stays with the old postman and a migration code is needed: see the chapter A client coming from another provider. - 4
Receiving invoices: webhook per tenant
Set up a webhook (and/or e-mail) for each company and fetch the signing secret:When an invoice is received, you get a signedbashcurl -X PUT https://peppol.verteco.digital/api/v1/companies/{id}/notifications -H 'Authorization: Bearer vpt_8f2a…' \ -H 'Content-Type: application/json' \ -d '{"webhookUrl":"https://vasa-saas.sk/peppol/webhook","notificationEmail":"…"}' curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/webhook/secret -H 'Authorization: Bearer vpt_8f2a…' # → { "secret": "…" } (store it to verify X-Verteco-Signature)POST(eventinvoice.received; likewiseinvoice.sent,invoice.delivered,invoice.rejected,invoice.undeliverable,invoice.reported,invoice.report_pending,invoice.report_rejectedandcompany.*for all your clients), durable with retry/dead-letter. For signature verification, the payload and the „Otestovať webhook" (Test webhook) button, see Notifications & webhooks. - 5
Sending invoices
Send the invoice (UBL Peppol BIS 3.0) through the national interface SAPI-SK 1.0:POST /sapi/document/send(OAuth2 client_credentials,client_secret= yourvpt_token). We add the Slovak tax report (TDD/C5) automatically. - 6
Usage data for re-invoicing
GET /companies/usage?month=YYYY-MMreturns the counts of sent and received documents for each of your companies separately as well as in total, in exactly the units the price list is built on, so you can re-invoice your clients directly from it. Without the parameter it returns the current month; the period is half-open and the response explicitly contains thefrom/tofields so you do not have to guess the month boundary. Companies with no traffic are listed with zeros. - 7
Scaling and robustness
Paginate lists:GET /companies?page&limitas well asGET /companies/{id}/documents?page&limit(with headers such asX-Total-Countand others). Respect the per-token rate limit: during bulk onboarding, batch with backoff on 429 (Retry-After) and handle409 ico_taken(idempotently).
POST /companies/{id}/verification is only a manual fallback when the FS webhook did not arrive and does not trigger the SMP registration). Whether the network really delivers to us is told by smpRegistered in GET /companies/{id}: true = registration in the national SMP confirmed, false = another provider holds the record (event company.smp_registered_elsewhere, a migration code is needed); sending-verified = the company may send (after a verified selection on the FS portal, or as a fallback after the verification token has been submitted via the API). The webhook for a received invoice is only sent once the company is active.FS webhook for intermediaries (integration manual)
If you are registered as an intermediary (sprostredkovateľ) (application via /sprostredkovatel/ziadost), the Financial Administration of the Slovak Republic (Finančná správa, FS) sends a notification to your webhook URL every time a client selects you on the FS portal (VPDS). This manual describes the exact contractas FS actually calls it in production (verified on live selections). FS does not publish its own public manual for the webhooks; this is what you need for the implementation.
1 · What the notification looks like + 2 · authenticity verification
🔒 The exact contract (payload, signature header) is shown after signing in
We keep integration details out of public HTML. Sign in with a free account and this part loads right here.
3 · What to do with it: forward the raw body to us
A reseller listing presumes a system of your own that receives the clients' selections and forwards them to us. Do not put our address on the FS form: FS must call you. Without a system of your own you are not a reseller; the right path is a registration page with your tag and a commission (/affiliate).
The recommended (and simplest) implementation is a raw-byte proxy: accept the POST, respond quickly, and forward the raw body bytes to your registration endpoint with us. That endpoint is created automatically once you fill in the /sprostredkovatel/ziadost form, it is active immediately, and you can see its exact URL (with your key) after logging in via GET /api/v1/resellers/me:
POST https://peppol.verteco.digital/peppol/webhook/reseller/{vas-kluc}
Content-Type: application/json
x-pds-secret: <unchanged value from the FS request>
(body = the unchanged raw bytes from FS)We cryptographically verify the verification_token, automatically create the company under your partner account (white-label), register it in the central SK SMP, and from that moment on we deliver e-invoices to it. You may store the contact details from the payload for your own onboarding; nothing more is needed. Exception: if the subject already had another provider, the SMP record stays with them and you receive the event company.smp_registered_elsewhere plus an e-mail from us; how to resolve it is described in a separate chapter. The event company.client_registered_directly arrives when your client adds their company directly in the Verteco portal: linked = true means we attached their account as a co-owner (the company contact e-mail matched), false is only an information about the attempt (at most once a day); nothing changes in your relationship with the client or in billing. The event company.released_to_direct arrives when your client selects Verteco directly on the Financial Administration portal: the company then leaves your programme (your accounts lose access, white-label is cleared, the selection contact is the owner); we inform the client from the portal and you also get an e-mail. Applies to selections from 21 September 2026. The opposite direction, a selection under your brand for a company your partner account already owns (for example your own company added as a direct customer), does not re-register the company: the access point and the record in the central SMP stay the same (the repeated registration is only a confirmation), the company merely switches to white-label mode, from then on it gets no e-mails about documents from us, you learn about them only through webhooks and the API, and it is billed under the partner model; archive and settings remain. This is a handy way to verify your integration on your own company.
Transport key (x-pds-secret header): after publishing your listing the FS e-mails a zip WebhookSecret_Verteco_<name>.zip to the applicant, i.e. to us, and texts its password to the phone number you gave in the form /sprostredkovatel/ziadost (the FS form of 17 September 2026 has a dedicated field for it), i.e. to you. Download the zip in the partner console, tab FS webhook (button Download the FS zip), open it with the password from the SMS and enter the 32-character key from the text file in the same place, where you can later show it or download it as .txt (every reveal is written to your account's audit log). If the SMS did not arrive, write to us. For listings filed before 17 September 2026 the password came to us and we stored the key ourselves. From the moment the key is stored we verify that the signature sha512(body + key) of every call matches. Until the key is entered we only record the signature. On save the key is compared with the calls the FS has already made to your endpoint and a key that contradicts them is refused; your proxy must therefore forward the body byte for byte (no JSON re-serialisation).
3a · What our endpoint answers
We process the call at once (synchronously). On purpose, the body of a 200 does not reveal whether the token was valid or whether a company was activated, so the endpoint cannot be used to find out which DIČ is our customer. You learn the result from the company.activated event on your notification webhook, and you can read it any time via GET /api/v1/resellers/me/clients (status active, peppolParticipantId filled in).
| Response | Meaning | Retry? |
|---|---|---|
POST · 200 {"status":"received"} | Received, stored in our audit log and processed. It confirms receipt, not the result: we answer the same when the token is invalid, the body is incomplete or not JSON, or our processing fails internally. | Not needed. Repeating the same call is always safe: the same selection creates no second company and no second account. |
POST · 403 {"status":"forbidden"} | The x-pds-secret header is missing or does not equal sha512(raw body + your FS key). Nothing was processed. | Not without a fix, the same call fails again. Fix the forwarding (original header, unchanged body) and send the stored original FS call. |
POST · 413 {"status":"too_large"} | The body is larger than 64 KB (an FS selection has about 1 KB), so something else was sent. | No. |
GET/POST · 404 {"status":"unknown"} | The key in the URL is malformed. | No, fix the URL. |
429 {"error":"rate_limited", …} | More than 600 calls per minute from one IP address; headers Retry-After and RateLimit-*. Nothing was processed. | Yes, the same call after Retry-After. |
405 {"error":"method_not_allowed", …} | A method other than GET or POST. | No, fix the method. |
502 / 503 / 504 (HTML or text) | The infrastructure in front of the application, for example during a deployment; the call may not have reached us. The same applies to a network timeout. | Yes, with a backoff. It is safe because processing is idempotent. |
GET · 200 {"status":"ready"} | The key is known and active. | – |
GET · 200 {"status":"unknown_reseller"} | The key is well formed but not registered or not active. A POST to such a key answers 200 received, but nothing is processed. | Check the key in the forward URL. |
If company.activated does not arrive within a few minutes after a 200, send us the time of the call; every call is kept in our permanent audit log.
3b · Verifying the request on your side before forwarding
We recommend that your proxy verifies every request before forwarding it. That way you send us only genuine FS selections, and random or forged calls do not use up your limit.
- Store first the whole request (raw body and headers). FS never sends a selection again, so the stored copy is the only way to recover it.
- Verify the
x-pds-secretheader: it must equalhex(SHA-512(raw body bytes + your FS key)), 128 hexadecimal characters. Hash the body exactly as it arrived, before any JSON parsing; append the key directly after the body with no separator; compare in constant time, ignoring upper and lower case. - Forward only matching requests, with the body unchanged and the original
x-pds-secretheader. Drop bodies over 64 KB; an FS selection is about 1 KB.
import crypto from 'node:crypto';
// rawBody = Buffer with the body exactly as it arrived (e.g. express.raw({ type: '*/*' }))
function isFromFs(rawBody, header, fsKey) {
const expected = crypto.createHash('sha512')
.update(Buffer.concat([rawBody, Buffer.from(fsKey, 'utf8')]))
.digest('hex');
const got = String(header || '').trim().toLowerCase();
return got.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}The same check in PHP, Python and C#:
<?php
// $rawBody = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_PDS_SECRET'] ?? null;
function isFromFs(string $rawBody, ?string $header, string $fsKey): bool
{
$expected = hash('sha512', $rawBody . $fsKey);
$got = strtolower(trim((string) $header));
return hash_equals($expected, $got);
}import hashlib
import hmac
# raw_body = the body bytes exactly as they arrived (Flask: request.get_data(), FastAPI: await request.body())
def is_from_fs(raw_body: bytes, header: str | None, fs_key: str) -> bool:
expected = hashlib.sha512(raw_body + fs_key.encode("utf-8")).hexdigest()
got = (header or "").strip().lower()
return hmac.compare_digest(expected, got)using System.Security.Cryptography;
using System.Text;
// rawBody = the body bytes exactly as they arrived (ASP.NET Core: copy Request.Body into a MemoryStream before reading any JSON)
static bool IsFromFs(byte[] rawBody, string? header, string fsKey)
{
byte[] key = Encoding.UTF8.GetBytes(fsKey);
byte[] data = new byte[rawBody.Length + key.Length];
Buffer.BlockCopy(rawBody, 0, data, 0, rawBody.Length);
Buffer.BlockCopy(key, 0, data, rawBody.Length, key.Length);
string expected = Convert.ToHexString(SHA512.HashData(data)).ToLowerInvariant();
string got = (header ?? "").Trim().ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(got));
}- Key: it is the same FS key we use to check the signature (from the FS zip, opened with the password from the SMS); once entered it is in the partner console, tab FS webhook. Keep it server-side only. A match on your side means a match on ours.
- There is no other mechanism: FS does not sign the transport with an asymmetric key and there is no public key for the header. The
verification_tokenin the body is an FS signature over the tax IDs; we verify it against the FS certificate, you just forward it unchanged. - Limit: the 600 requests per minute from one IP address apply to every call to
/peppol/webhook/…, valid or not. A call without a valid header gets 403 from us and nothing is processed, but spam would use up your server's limit. - If the signature does not match on the first real selection, do not discard the request: store it and write to us, and we will find the cause together.
4 · Operating rules (important)
- Idempotency: the same entity may repeat the selection; processing the same DIČ (Slovak tax identification number) must be safe (it is on our side).
- When forwarding fails (for example a 403 or an outage of your server), FS does not repeat the selection. Send us the stored original body again with its original
x-pds-secretheader; processing is idempotent and the company is activated. If you did not store the call, write to us. - Source IP: calls come from FS infrastructure (observed from
194.1.2.13); we recommend an IP allowlist only as a supplement, not as the sole protection (FS does not guarantee the range). - Response: return
200even on an internal processing error (log the error yourself); FS does not evaluate anything else. - Deployment order: the webhook must be live before the application is filed with FS; the first selection may arrive shortly after publication.
A reference proxy implementation is ~30 lines (accept POST → store → forward the raw bytes). Check the URL with a GET on your registration endpoint (answer ready). Once your key is stored, a test POST without a valid x-pds-secret header gets 403 and creates nothing; try the whole chain including the company activation in the test environment test.peppol.verteco.digital (simulated selection at /register-vpds-test; the reseller registration there has no fee and no FS filing, a company created with POST /companies is activated at once, and the simulated selection reaches your webhook as it would from FS). Questions: Support.
5 · Terminating the intermediary agreement
The intermediary agreement can also be terminated directly from within the application: the owner of the partner account, in Nastavenia poštára (Postman settings), fills in a termination request (control question + confirmation of the consequences) and confirms the notice via a link sent to them by e-mail. Upon confirmation the notice is deemed delivered; the notice period is one month and runs from the 1st day of the following month (Art. 7.1 of the Terms and Conditions of Intermediation). We file the request for removal from the list of intermediaries with the Financial Administration ourselves within 5 business days after the agreement ends (Art. 7.3); our team receives the supporting record, nothing is sent to the intermediary's clients. An unconfirmed request can be cancelled in the application. Programmatically: GET/POST/DELETE /api/v1/resellers/me/termination.
6 · How responsibility is shared
A summary of Art. 5 of the Terms and Conditions of Intermediation (v1.8), so you know what we expect from you in practice. Neither a contractual penalty nor liability for damage arises for you from the mere technical transmission of your client's document, and you are not obliged to verify the economic reality or the factual correctness of individual documents (Art. 5.8). A selection on the Financial Administration portal confirmed with eID or login credentials, together with our verified verification token (Verifikačný údaj), is sufficient proof of the authority to act for the company, and giving your own clients access to the API does not count as making access available to a third party (Art. 5.9). Contractual penalties are capped at EUR 5,000 per event or series of breaches with one cause and EUR 10,000 per calendar year (Art. 5.3). Sanctions and measures that authorities or the Peppol network authorities actually impose on us for conduct within your sphere are still passed on without a cap (Art. 5.2), because we bear them towards the Financial Administration and OpenPeppol without limitation. The claim is, however, reduced in proportion to our share, we attach the underlying decision and a written calculation, you have 15 days to respond, a sanction that is cancelled or reduced is refunded to you within 15 days even without a request, and we assign to you our claims against the client who caused it, together with the supporting documents (Arts. 5.5 and 5.7). That is why Art. 4.1(h) asks you to pass the corresponding obligations on in your contracts with clients and to keep records of who created which document; we send a model clause free of charge on request.
Partner account: notification webhook, client management, billing model and termination
Partner notification webhook (intermediaries)
If you are a registered intermediary (sprostredkovateľ), you do not need to configure a webhook for each company separately: a single partner notification webhook receives all events of the companies under your partner account and takes precedence over the webhooks of individual companies. Your clients therefore configure nothing; you distinguish the company by companyDic / peppolParticipantId.
E-mails about individual invoices: the partner account is a member of every client company, so with the default account settings it would be mailed about every invoice received by each of them. White-label clients never get e-mails from us (the channel is the webhook); for the other companies you switch them off for the partner account in Nastavenia poštára (Postman settings) → Notifications (one switch; the same setting also lives in the account's Settings → Notifications).
/resellers/me/notification-webhookintermediary accountCurrent configuration: { url, hasSecret, events, lastRevealedAt, lastRevealedIp } (the secret is not returned).
/resellers/me/notification-webhookintermediary accountSets an https URL (max 512). On the FIRST setup a signing secret is generated and returned ONCE in the response; subsequent URL changes keep the secret and do not return it. An empty URL removes both the webhook and the secret.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | https URL, max 512; empty string = remove |
// 200 OK (first setup)
{ "url": "https://vasa-appka.sk/peppol/events", "secret": "vpt_…", "hasSecret": true,
"events": ["company.activated","company.deactivated","company.smp_registered_elsewhere","company.client_registered_directly","company.released_to_direct","invoice.received","invoice.sent","invoice.delivered","invoice.rejected"] }/resellers/me/notification-webhook/revealintermediary account + passwordRe-displays the stored secret after confirmation with the account password ({ password }). Every reveal is audited and the last one is visible in GET.
The X-Verteco-Signature signature is computed the same way as for the company webhook (below), only with the partner secret.
Managing clients via the API (unilateral release and sending brake)
A client who leaves a partner usually does nothing, which is why these operations are unilateral and require no cooperation from the client. Invoice reception is not affected by them: it is tied to the company's registration in the central SMP, not to the managing account.
/resellers/me/clients/{companyId}/releaseintermediary accountDetaches the company from the partner account with immediate effect. A company verified by the Financial Administration (or active, with an SMP record or with documents) passes under direct management by the platform: its registration, verification and invoice reception continue without interruption and the partner is no longer billed for it from this moment on (outcome=released). A company that was never verified, is not active and has neither an SMP record nor any document (for example one registered by mistake) is removed completely so that it can be registered afresh; its IČO and DIČ are free again (outcome=deleted; since 6 October 2026). Irreversible from the partner's side.
/resellers/me/clients/{companyId}intermediary accountRemoves a company registered by mistake: completely, including access, and frees its IČO and DIČ for a fresh registration (by the client or by you). Allowed only for a company that was never verified on the Financial Administration portal, is not active, has no record in the central SMP and has no document; a real customer record cannot be removed (409 client_not_removable with the reason), use release for it.
/resellers/me/clients/{companyId}/pause-sendingintermediary accountSafeguard when ending a cooperation: blocks sending of the company's documents (SAPI returns 403 SAPI-AUTH-003 with the pause reason, the portal API 403 sending_paused); reception continues. Immediate effect.
/resellers/me/clients/{companyId}/public-linksintermediary accountTurns "download without sign-in" on/off for a client (body {"enabled":true,"acknowledge":true}; acknowledge is required when enabling = you confirm the client instructed it and that the URL is the credential). Turning off revokes all active links at once. Then request a link per received document via POST /sapi/document/receive/{documentId}/public-link – it returns url (landing page), xmlUrl, htmlUrl and pdfUrl.
/resellers/me/clients/{companyId}/resume-sendingintermediary accountLifts the sending pause.
/resellers/me/terminationintermediary accountStatus of a termination notice of the intermediary agreement filed from the application (204 = none; otherwise status awaiting_email / confirmed, contract end date contractEndsOn).
/resellers/me/terminationintermediary account (owner of the registration)Files a request to terminate the agreement: body { confirmName: the exact name of the registered intermediary, reason?: string, acknowledged: true }. A confirmation link (48 h) is sent to the account owner's e-mail; the termination notice is served only once it is confirmed (Art. 7.1 of the Intermediary Terms, OP). 202 + status; 400 confirm_name_mismatch / acknowledgement_required; 409 termination_pending / termination_confirmed.
/resellers/me/terminationintermediary accountCancels a request that has not yet been confirmed. A confirmed termination notice cannot be cancelled from the application (409); write to peppol@verteco.digital.
Partner billing model
In Nastavenia poštára, tab Fakturácia (and via GET/PUT /api/v1/resellers/me/billing) you choose the billing model (the choice is available only to registered intermediaries): per_company = €2 per month per actively sending company registration number (IČO), or per_document = €0.01 for every invoice sent by your companies (received documents are free) with a minimum monthly billing of €300 + VAT; the 1,000-documents-per-month limit does not apply under per_document, every sent invoice is billed from the first one (a credit note is a sent document) and received documents are free without a cap. A model change always takes effect from the 1st day of the following month (in the response pendingModel and pendingFrom); the response of both calls also returns a recalculation of the current month under both models, so you switch informed.
Calculator: which model comes out cheaper for your number of companies and documents, including the yearly total with the registration and VAT, is worked out on /en/sprostredkovatel (Calculator section).
Monthly consumption statement per client: GET /api/v1/resellers/me/usage?month=YYYY-MM (the previous month without the parameter) returns, for every company under your brand that your account owns, the counts of sent and received documents, the documents over the 1,000 limit (sent and received together), the amount under model A (€2 for a company that sent a document in the month plus €0.01 for every document over the limit; the archive is never charged to a company under your brand) and under model B (€0.01 per sent document), the totals of both models with the top-up to the €300 minimum under model B, and the model that applied to you in that month (appliedModel); direct companies your account is a member of pay from their own credit and are not on the statement. Amounts are in cents excl. VAT, priced the same way as the monthly settlement but from the current state of the companies at the time of the read (the binding amounts are the settlement entries in your credit), and for companies over the limit they differ from the current-month preview in GET /resellers/me/billing, which computes model A as €2 × active companies. With ?format=csv you get the same as a file for your accountants (UTF-8 with BOM, semicolon separator, Slovak headers, amounts with a decimal comma, a final Spolu row), and the partner console shows it on the Consumption tab with the last 12 months to pick from.
Recommended procedure for a terminated client (a cap on costs on your side): you can see current usage in GET /api/v1/companies/usage?month=YYYY-MM (sent/received breakdown per company, exactly for re-billing) and in GET /api/v1/resellers/me/clients (counts for the current month); in addition, an event is sent to the partner webhook for every document your companies send or receive, so you catch a "terminated" company at its very first document. Then simply call pause-sending or release.
Partner integration architecture: document copies, webhooks, limits
The recommended flow for a partner's own portal or application (from partner questions in October 2026):
- Keep copies of the documents yourself. Companies under your brand have the Data archive switched off by default, so the document content (XML, PDF) stays with us for 14 days after delivery and is then deleted; the delivery evidence (receipt, timestamps, report state) remains. Your portal therefore reads from its own copy and fetches only new documents from us.
- New documents by webhook, not by polling. The partner notification webhook (above) receives
invoice.receivedfor every company; fetch the document withGET /sapi/document/receive/{id}(XML, PDF, HTML and attachments have their own paths) and confirm it withPOST /sapi/document/receive/{id}/acknowledge. The fallback is paged reading,GET /sapi/document/receive?status=received&limit=200withpageToken(oldest first; acknowledged documents drop out of the filter). - States of sent documents arrive by webhook as well (
invoice.sent,invoice.delivered,invoice.rejected,invoice.undeliverable,invoice.reported) and can be queried withGET /sapi/document/sent. - Your own domain for clients. Your application may call our API through its own bridge (for example api.your-domain.sk) with one partner key for all companies under your account; clients never need our address and you forward webhooks to them from your side.
- Limits. 300 calls per minute per API key, the
RateLimit-*headers return the remainder; a higher limit is set on request. The limit of 1,000 documents per month per company applies in model A (sent and received together, each further document €0.01); it does not apply in model B.
Which remuneration model: model B (€0.01 per sent document, minimum €300 per month, received free) is cheaper than model A (€2 per company that sent a document in the month) once more than 150 companies send in a month and the average sent per sending company stays under 200 documents (150 companies = up to 30,000 documents, 300 companies = up to 60,000); you switch the model in the partner console with effect from the following month.
How long we keep client documents
retention field in the API (mode, contentAvailableUntil), so the deadline can be scheduled programmatically.A client's data archive through the partner API: if your portal has to switch a client's archive on or off, it does not have to send the client to our portal.
/resellers/me/clients/{companyId}/archive-settingsintermediary account (partner key or session)Same body as PUT /companies/{id}/archive-settings: archiveReceived, archiveSent (true/false), wipeDaysReceived, wipeDaysSent (1 to 90 days), legacy wipeDays for both directions; a field you do not send stays as it is. Only for companies under your brand that your account owns; a foreign company and your own direct company alike are 404 client_not_found (your own company is set in the portal). Every change is written to the company's settings ledger with your account, IP and User-Agent as the actor, exactly like a change from the portal; switching off or shortening the window never wipes a document sooner than a full window from today. Response: companyId, legalName, archiveReceived, archiveSent, wipeDaysReceived, wipeDaysSent, storageUsedBytes (nightly measurement, null until measured).
What a switched-on archive means for the price of a company under your brand, exactly per the settlement code: a switched-on Data archive never triggers the €2 base for a company under an intermediary's brand, so under model A you pay €2 only for a month in which the company sent a document, plus €0.01 for every document over 1,000 (sent and received together), and under model B €0.01 per sent document; switching the archive on therefore changes only how long we keep the originals (for the contract term instead of the window), not the amount. Storage is measured once a night and the notices at 80 % and at a further started GB go to the partner's contact e-mail (falling back to the registration owner's e-mail), never to the client; the monthly settlement carries no storage line. The fields archiveReceived, archiveSent, wipeDaysReceived, wipeDaysSent and storageUsedBytes are also on every company in GET /resellers/me/clients.
Peppol Directory: the client's business card and how to switch it off
/resellers/me/clients/{companyId}/directory-listingintermediary accountCurrent state of the client's business card read from the SMP: available (the SMP answered), registered (the company has a record under our account), listed (true/false, null until a record exists), defaultListed (what registration sets for this DIČ).
/resellers/me/clients/{companyId}/directory-listingintermediary accountBody {"listed": true|false}. Written straight to the SMP and recorded on the company (who, when, from where). 409 participant_not_registered until the company has a record, 502 directory_unavailable when the SMP does not answer.
A client coming from another provider: detection, migration code, takeover
Slovakia has one central SMP for all postmen and every participant has a single record in it. When your client selects your brand on the Financial Administration portal, we verify and activate the company, but if its record is already held by the previous provider, the network keeps delivering to them. The switch is done with a one-time migration code the client obtains from the current postman (the standard SML mechanism, FS FAQ example 46; under the PA SK rules the old postman must deregister the client from the central SMP within 3 working days after the contract ends). Note: under the FS FAQ (examples 36 and 73) a client may keep receiving with another postman and only send through you; if that is intended, call PUT /resellers/me/clients/{id}/receiving-provider with elsewhere=true (state smpState = "external") and both the reminders and the automatic registration stop. This chapter shows how to detect, store and resolve the situation in your platform and what you receive from us along the way.
GET /companies/{id} returns status = "active", verified = true, but smpRegistered = false and smpState = "elsewhere". Documents go to the old postman until the code is entered. A white-label client never receives e-mail from us; informing them is up to you (we send you the facts, see step 5). If the client deliberately receives elsewhere and only sends through you, mark it with PUT /resellers/me/clients/{id}/receiving-provider (elsewhere=true): the state becomes external and nothing below applies. Since 8 October 2026 this also works before the FS selection: mark a client you created with its DIČ up front (status pending_verification) with elsewhere=true and its later selection on the FS portal activates it without the automatic SMP registration (smpState stays external). The client switches receiving on later on the Sieť Peppol tab of the company detail, or you send elsewhere=false. The portal itself has no such control before the selection, and companies created by the selection itself register as before. Since 9 October 2026 a partner can also declare it when creating the client: POST /companies with whiteLabel: true and receivingElsewhere: true (one step instead of two; the flag exists before a forwarded selection can arrive). Order matters: set the flag before you forward the FS selection; a registration retry chain already running stops once it sees the flag, and GET /resellers/me/clients shows the state. In the test environment a company with a DIČ is activated and registered at creation, so a later elsewhere=true comes too late there: use the flag at creation and the company ends up active and verified with state external and no registration, exactly as in production.- 1
Prepare the receiver: partner webhook
Configure one webhook for all your clients viaPUT /resellers/me/notification-webhook(above). VerifyX-Verteco-Signatureand handle the eventcompany.smp_registered_elsewhere: it means "another provider holds the record, a migration code is needed". De-duplicate oncompanyId+event; the event repeats weekly until the client enters the code.json// POST to your webhook, headers X-Verteco-Event + X-Verteco-Signature { "event": "company.smp_registered_elsewhere", "occurredAt": "2026-09-03T15:04:05Z", "companyId": "bb6eb4a7-1a98-48fe-97e6-e5abbaa56e54", "companyDic": "SK1028310426", "peppolParticipantId": "0245:1028310426", "status": "active", "action": "migration_code_required", "migrateUrl": "https://peppol.verteco.digital/dashboard/companies/migrate" } - 2
Add a lookup: when to ask and what to read
The webhook may be missing (not configured yet) or lost during an outage, so read the state as well:GET /resellers/me/clientsreturnssmpRegisteredandsmpStatefor every company; the detail is inGET /companies/{id}. Recommended rhythm: once a day for all clients, when the client page opens in your app, and 2 to 3 minutes after acompany.activatedevent (SMP registration usually completes within a minute). Before creating a client,GET /public/peppol-check?id=0245:<DIČ>pays off: if the tax number is already in the network, the client will need a code and you can tell them in advance.smpState Meaning What to do registeredThe SMP record is under our account, the network delivers to us. Nothing, the client receives. pendingRegistration is running (seconds; hours during an SMP outage; we retry automatically). Wait, re-read in a few minutes. elsewhereAnother provider holds the record; the client needs a migration code. Show the client a prompt, obtain the code, call smp-migrate (step 4). rejectedThe FS verification token was refused. The client must repeat the selection on the FS portal. nullNot applicable (the company is not yet verified by the FS selection). Lead the client to the FS portal selection. - 3
Store the state on your side
Keep per client:companyId,peppolParticipantId,smpState,smpStateAt(when you last saw it) andnoticeShownAt(when you informed the client). Onelsewhereshow a persistent notice in your app with a field for the code; do not block sending, the company may send. Onregisteredremove the notice.text// handling the event in your backend (pseudo-code) on webhook(event): verify X-Verteco-Signature == sha256=HMAC(secret, rawBody) // else 401 if event.event == "company.smp_registered_elsewhere": tenant = tenants.byCompanyId(event.companyId) tenant.smpState = "elsewhere"; tenant.smpStateAt = event.occurredAt showBanner(tenant, "Vyžiadajte si migračný kód od doterajšieho poskytovateľa") // + input if event.event == "company.activated": schedule(in 3 min): tenant.smpState = GET /companies/{id}.smpState daily job: for row in GET /resellers/me/clients: tenants[row.companyId].smpState = row.smpState on code entered by the client: r = POST /companies/{id}/smp-migrate { migrationCode } if r.status == 200: tenant.smpState = "registered"; hideBanner(tenant) else: showError(tenant, r.error) // 400 migration_code_rejected → ask for a new code - 4
Obtain the code and take over the record
The client asks the current provider for the SMP migration code for their tax number; the code is single-use and time-limited, use it right away. CallPOST /companies/{companyId}/smp-migratewith your partner token (you own the company). A 200 response carries the company withsmpRegistered = true; the network switches immediately with no downtime. No separate event is emitted: record the state from the response or re-read it via GET.bashcurl -X POST https://peppol.verteco.digital/api/v1/companies/<companyId>/smp-migrate \ -H 'Authorization: Bearer vpt_8f2a…' -H 'Content-Type: application/json' \ -d '{ "migrationCode": "MIGR-7K3Q-…" }' # 200 { "company": { …, "smpRegistered": true, "smpState": "registered" }, "outcome": "REGISTERED" }HTTP · code Meaning What to do 400 migration_code_rejectedThe code is invalid, used or expired. Ask the old provider for a new code. 409 registered_elsewhereThe SMP did not accept the code; another provider still holds the record. Check the code belongs to this tax number; retry with a new code. 403 company_not_verifiedThe company is not verified by the FS selection yet. FS portal selection first. 503 smp_unavailableThe central SMP does not respond. Retry in a few minutes (the code stays valid). - 5
What we send you
On detection we e-mail the partner contact address (from the intermediary registration) with the tax number, Peppol ID and the procedure, then weekly until the takeover happens (at most four times); the same information goes out as the webhook. We never write to a white-label client. If you receive the code but do not want to call the API, send it to peppol@verteco.digital and we perform the takeover from the administration.Text for the client you can reuse:
Your company is already activated with us, but the record in the national Peppol register (SMP) is still held by your previous provider, so e-invoices currently go to them. Ask them for the SMP migration code for tax number … and enter it here. The switch is immediate and has no downtime.
POST /companies/{id}/sandbox/smp-elsewhere (owner/admin) or POST /resellers/me/clients/{companyId}/sandbox/smp-elsewhere (partner account) puts the test company into smpState = "elsewhere" and sends company.smp_registered_elsewhere to your webhook and by e-mail exactly as in production; you then complete the take-over with POST /companies/{id}/smp-migrate and the code SANDBOX-MIGRATION-OK. The same button sits in the test company's detail. In production both simulation endpoints do not exist (404) and the SMP refuses the code.Leaving for another provider and moving your clients
If you later move from us to another certified provider, your clients go with you. We handle the technical hand-over of the registrations in bulk and without downtime; one step, however, is set by the Financial Administration, not by us, and applies equally to every provider in Slovakia. The freedom to leave is a guarantee, not a scenario we plan for: our partner guarantee (we match the listing fee down to zero and keep the yearly price per document 10 % below any comparable offer) holds for the whole cooperation, so there is no reason to leave in the first place.
- 1
Notice from the console
In Postman settings you file the notice and confirm it via the e-mail link; the notice period is one month from the 1st day of the following month (art. 7.1). Everything keeps running until the contract ends. - 2
Migration codes in bulk
Send us the list of your clients’ tax IDs. We issue the codes from the central SMP for all of them at once (not one by one in the portal) and hand them to you for the new provider. Alternatively we deregister the clients within the statutory 3 working days after the contract ends (PA SK rules). - 3
Take-over in the SMP without downtime
The new provider registers each client in its SMP with the migration code; the SML rebinds immediately and our record becomes “Unpublished”. Documents keep flowing throughout: in Slovakia every subject has one SMP record for receiving but may send through several providers, so the new provider can send for the client as soon as it holds the client’s verification token, even before it takes over receiving. - 4
Selecting the new provider on the Financial Administration portal
The verification token the FS issues when a provider is selected is its signature over the pair provider tax ID and client tax ID, so it is bound to one specific provider. The central SMP requires it for every registration of a Slovak participant, with a migration code too. The new provider therefore needs a verification token issued for itself, and that only comes from a selection on the FS portal: by eID or with FS portal credentials, and an authorised person (for example the accountant) may do it. It is the same authorisation (power of attorney) under which VAT returns and the control statement are filed, and the same action as the first selection. The migration code removes downtime and the wait for deregistration; it does not replace the FS selection. - 5
Data export
On request we export in bulk the original XML documents, receipts and metadata we hold at the time the contract ends; we do not delete them earlier than 90 days after the end or until you confirm the export (art. 7.4).
Contractual basis: art. 7.1, 7.3 and 7.4 of the intermediary terms. The opposite direction (a client arriving from another provider) is the previous chapter.
Changing an intermediary's data at the Financial Administration
The Financial Administration holds the data from the form we filed for you as an intermediary (business name, seat, company ID, tax ID, contact person, e-mail, phone, web, the address of your webhook). You do not request a change by e-mail: in the intermediary console open the Data at the FS tab, enter the new values next to the current ones, leave the rest as they are and confirm all changes at once. We send a request signed by Verteco's managing director together with the updated form to peppol@financnasprava.sk and inform you of the outcome by e-mail (account owner and contact address).
In the FS list and selection you appear under the business name of the legal entity from the form; the form does not know a product name. You fill in your brand, website and service description in the intermediary console on the Information tab, and that is how you appear on /partneri and /nasi-sprostredkovatelia (brand large, business name below); the card exists from the day of filing.
Requests leave on working days between 08:00 and 16:00 (Slovak public holidays excluded), never earlier than 2 hours after filing; until then (and outside the window until the next dispatch) the request waits and can be cancelled. A change of business name, seat or country is first checked by our team (supporting document, e.g. a register extract) and leaves only after that check. One request per 48 hours applies and only one may be open (waiting or handed to the FS) at a time, so put all changes into it. The same through the API: GET /resellers/me/fs-registration (current values, open request, history, dispatch window), POST /resellers/me/fs-change-requests with changes, note and confirm: true (429 with Retry-After during the 48 h), DELETE /resellers/me/fs-change-requests/{id} while the request is waiting.
| Field | Changeable | How we recognise it was applied |
|---|---|---|
Business name, e-mail, phone, web | yes | automatically from the public FS list (checked every morning); the status becomes “applied” and an e-mail follows |
Seat, contact person, webhook URL | yes | our team confirms once the FS replies (not in the FS list) |
Company ID, tax ID | no | a different identity means a new intermediary registration |
Intermediary terms and their changes
Changes to the Intermediary Terms for the delivery service are published on the terms page (also as a PDF) and announced by e-mail to the account owner and the contact person. Under clause 9.1 they do not require your consent and apply from the day of publication; the partner console neither prompts for nor blocks on them. If you disagree with a change you may terminate the contract under clause 9.2 by notice to [email protected] with immediate effect and without penalty.
On the “Terms” tab, state where you published the customer information under clause 4.4 (the Annex A information in your terms, on your website and in your software at the e-invoice activation point); for existing contracts within 30 days of the change notice at the latest. Our team checks the presentation (clause 4.5) and the result appears on the same tab.