← Back to all articles

How Peppol SMP and SML discovery actually works

SealDoc Team · · 5 min read

Before a Peppol invoice can be delivered, the sender’s system needs to find the receiver’s Access Point. There is no central registry you can query directly. Instead, Peppol uses a two-level discovery chain that works like DNS.

Understanding this chain is essential if you are building direct Peppol integration rather than delegating routing to an Access Point provider. Even if you use a provider, knowing the discovery mechanism helps debug delivery failures.

The two components

SML (Service Metadata Locator) is the root of the chain. It is a DNS-based service operated by OpenPEPPOL. Its job is to tell you where to find a participant’s SMP.

SMP (Service Metadata Publisher) is a participant-level service. Each Peppol Access Point runs an SMP for its registered participants. The SMP publishes what document types a participant can receive and the endpoint URL where those documents should be delivered.

The full discovery flow:

  1. You have a participant identifier (for example, a Belgian enterprise number)
  2. Hash and encode it into a DNS hostname
  3. Query the SML DNS zone to find the participant’s SMP hostname
  4. Query the SMP to get the service metadata for the target document type
  5. Extract the endpoint URL and the receiver’s certificate
  6. Deliver the signed AS4 message to that endpoint

Step 1: constructing the DNS lookup

The Peppol participant identifier has two parts: a scheme code and a value. For a Belgian company identified by its enterprise number:

Scheme: 0208
Value:  0468863455
Full:   iso6523-actorid-upis::0208:0468863455

To turn this into a DNS hostname:

  1. Lowercase the full participant identifier
  2. Compute the MD5 hash
  3. Encode the hash in Base32 (no padding, lowercase)
  4. Prepend b- (the Peppol SML prefix)
  5. Append the SML domain: edelivery.tech.ec.europa.eu

In C#:

static string BuildSmlHostname(string participantId)
{
    var lower = participantId.ToLowerInvariant();
    var hash  = MD5.HashData(Encoding.UTF8.GetBytes(lower));
    var b32   = Base32.ToBase32String(hash).ToLowerInvariant().TrimEnd('=');
    return $"b-{b32}.iso6523-actorid-upis.edelivery.tech.ec.europa.eu";
}

Base32 encoding is RFC 4648. The .NET standard library does not include Base32; use a small utility or the SimpleBase NuGet package.

Step 2: the SML DNS query

Query the hostname as a CNAME. The response gives you the SMP hostname.

static async Task<string> LookupSmpHostname(string smlHostname)
{
    var result = await Dns.GetHostEntryAsync(smlHostname);
    // The CNAME target is the SMP hostname
    return result.HostName;
}

If the DNS query fails (NXDOMAIN), the participant is not registered in the Peppol network. This is a definitive “no such participant” answer, not a transient error. Log it and surface it to the user rather than retrying.

Step 3: querying the SMP

The SMP endpoint URL is constructed from the SMP hostname, the participant scheme, and the participant value. The Peppol SMP specification (version 2.0) defines the URL structure:

https://{smp-hostname}/{scheme}%3A%3A{value}/services/{doctype}

The :: separator between scheme and value is percent-encoded. The document type identifier is also percent-encoded.

For a Peppol BIS Billing 3.0 invoice:

doctype: urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1

Full example request:

GET https://{smp-hostname}/iso6523-actorid-upis%3A%3A0208%3A0468863455/services/urn%3Aoasis%3Anames...

The SMP returns a signed XML document (the service metadata). Here is what the response looks like, stripped to the relevant parts:

<ServiceMetadata>
  <ServiceInformation>
    <ParticipantIdentifier scheme="iso6523-actorid-upis">0208:0468863455</ParticipantIdentifier>
    <DocumentIdentifier>urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::...</DocumentIdentifier>
    <ProcessList>
      <Process>
        <ProcessIdentifier scheme="cenbii-procid-ubl">urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</ProcessIdentifier>
        <ServiceEndpointList>
          <Endpoint transportProfile="peppol-as4-2.0">
            <EndpointURI>https://ap.example.com/as4</EndpointURI>
            <Certificate>MIIBxTCCA...</Certificate>
          </Endpoint>
        </ServiceEndpointList>
      </Process>
    </ProcessList>
  </ServiceInformation>
  <Signature>...</Signature>
</ServiceMetadata>

Step 4: verifying the SMP signature

The SMP response is signed with the Access Point’s certificate. You must verify this signature before trusting the endpoint URL.

This is where most implementations run into trouble. SMP responses use XML Digital Signatures (XMLDSig). Many SMP servers re-serialize the signed document when serving it, which changes namespace prefix declarations. This breaks the reference digest in the signature.

The problem and its fix are covered in detail in XML Signature validation pitfalls in Peppol. The short version: use an XML parser with PreserveWhitespace = true and do not re-serialize the document before verifying.

After verification, extract the endpoint URL and the certificate:

var ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("wsa", "http://www.w3.org/2005/08/addressing");
ns.AddNamespace("bdxr", "http://docs.oasis-open.org/bdxr/ns/SMP/2016/05");

var endpointNode = doc.SelectSingleNode(
    "//bdxr:Endpoint[@transportProfile='peppol-as4-2.0']/bdxr:EndpointURI", ns);
var certNode = doc.SelectSingleNode(
    "//bdxr:Endpoint[@transportProfile='peppol-as4-2.0']/bdxr:Certificate", ns);

var endpointUrl = endpointNode?.InnerText;
var cert = new X509Certificate2(Convert.FromBase64String(certNode?.InnerText ?? ""));

Step 5: delivery

With the endpoint URL and certificate in hand, the invoice is wrapped in an AS4 message and delivered over HTTPS. AS4 is the Peppol transport protocol. It requires:

  • Signing the AS4 message with your Access Point’s certificate
  • Encrypting the payload with the receiver’s certificate (from the SMP response)
  • Sending to the EndpointURI from the SMP

AS4 implementation is outside the scope of this article. In practice, building your own AS4 layer is a significant engineering effort. Most organizations use a certified Access Point provider rather than implementing AS4 directly.

Caching

SMP lookups should be cached. The DNS TTL on the SML entry is typically 3600 seconds. The SMP service metadata does not change frequently. A 24-hour cache on SMP responses is reasonable for most production systems.

Do not cache negative results (NXDOMAIN) indefinitely. A participant may register after your first lookup. Cache negative results for 15 to 30 minutes, not days.

What can go wrong

NXDOMAIN from SML: the participant is not registered. This is permanent, not transient. Do not retry without investigation.

SMP returns 404: the participant is registered in the SML but does not have service metadata for the requested document type. The receiver’s Access Point may not have configured support for Peppol BIS Billing 3.0.

Certificate validation failure: the SMP response signature uses a certificate not in the Peppol PKI trust list. This should be treated as a security error, not a connectivity error. Reject the response.

Namespace-related signature failure: see XML Signature validation pitfalls in Peppol.

Endpoint returns AS4 SOAP fault: the invoice was delivered but rejected by the receiver’s Access Point. The fault code indicates whether the rejection is due to certificate mismatch, unsupported document type, or payload validation failure.

SealDoc and SMP discovery

SealDoc’s REST API performs the full SMP discovery chain internally. When you submit an invoice generation request with a Peppol participant ID, the API resolves the endpoint, validates the chain, and routes the document. If discovery fails, the API returns a structured error indicating which step failed. You do not need to implement SML or SMP lookup yourself.

For participants who do need to run lookups against the Peppol network for testing or auditing, the SealDoc public validator can verify whether a Peppol invoice is routable to a specific participant before you commit to delivery.


← Back to all articles