Print Service – Setup
Complete step-by-step guide to setting up the merchantCENTRAL Print Service: from installing the local print service and connecting it to Business Central to printing your first test label.
Architecture overview
Business Central (Cloud) cannot communicate directly with local printers. MC.PrintService is a lightweight local application installed on the customer's premises that receives print jobs and forwards them to Windows or Zebra printers.
┌──────────────────┐ Print job ┌──────────────────┐ PDF / ZPL ┌──────────────┐
│ Business Central │ ────────────────► │ MC.PrintService │ ─────────────► │ Printer │
│ (Cloud/SaaS) │ │ (on-premises) │ │ Windows/Zebra│
└──────────────────┘ └──────────────────┘ └──────────────┘
There are three transport modes for the connection from BC to MC.PrintService:
| Direct Push (default) | Relay (Option C) | Poll (Option D) | |
|---|---|---|---|
| How it works | BC calls the service directly via the fixed public IP | BC pushes to a queue in the customer's own Azure | The Function polls BC; BC stays passive |
| Inbound port forwarding | required | not required | not required |
| Fixed public IP | required | not required | not required |
| BC makes outbound calls | yes | yes (job incl. label) | no (only an optional wake-ping) |
| Customer's Azure subscription | no | yes (~€1–2/month) | yes (~€1–2/month) |
| Status | ✅ available | ✅ available | ✅ available |
Which mode should I choose?
Direct Push is the default and the best choice for most customers: free of charge, immediate status feedback, and simple to set up. It requires a fixed public IP and the ability to open a port. Relay and Poll are for customers without a fixed IP / without inbound port forwarding. The difference: with Relay, BC actively pushes the job into Azure; with Poll, BC stays fully passive (the Function fetches the jobs) — ideal when BC must not make outbound web service calls.
Prerequisites
- [ ] merchantCENTRAL Hub – installed and active
- [ ] Print Service App – installed (AppSource or deployed by ALTENBRAND)
- [ ] Module license – at least a demo license for
PRINTSERVICE - [ ] Windows PC or server on the customer's network for MC.PrintService (Windows 10/11 or Server 2016+)
- [ ] Printer – Windows printer driver (for PDF) or Zebra printer reachable via TCP/IP (for ZPL)
- [ ] For Direct Push additionally: fixed public IP and access to the router/firewall
Step 1: Install MC.PrintService
The local print service is installed once on a machine in the customer's network that runs continuously and can reach the printers.
Option A – Windows Service (recommended, PDF + ZPL)
- Extract the ZIP package.
- Double-click
Install.bat→ confirm the UAC prompt. - The script installs the service, configures autostart and a firewall rule, and automatically generates an API key that is displayed at the end.
- Note the API key – it will be required in Step 3 when configuring Business Central.
The service listens on port 5050 by default. Configuration UI in the browser:
http://localhost:5050/config.
Looking up the API key later
The key is stored in appsettings.json in the installation directory under
Security:ApiKey. The configuration UI displays at the top whether a key is set.
Option B – Docker (ZPL out of the box only)
cd service
# Set API key (required for remote access):
export MCPS_API_KEY="$(openssl rand -hex 32)"
docker compose up -d
The service is then available at http://localhost:5100. PDF printing inside the container
requires a CUPS sidecar; for pure ZPL operation (Zebra printer via TCP) Docker is ideal.
No remote access without an API key
If no Security:ApiKey is configured, the service accepts local requests only
(localhost) for security reasons. Requests from Business Central (Cloud) will be rejected
with 401. The key is therefore mandatory for remote operation.
Part A: Setting up Direct Push
With Direct Push, Business Central reaches MC.PrintService via the customer's fixed public IP. Three layers of protection secure the endpoint: API key, HTTPS, and a firewall restriction to Business Central.
A1 – Provide HTTPS
Business Central (Cloud) sends print jobs from the Azure data center and validates the server certificate. Self-signed certificates will not work. Choose one of the following options:
| Option | Description | Best suited for |
|---|---|---|
| Reverse Proxy (recommended) | Caddy, nginx, or IIS in front of the service, subdomain per customer (e.g. print.kunde.de → fixed IP), Let's Encrypt certificate obtained automatically |
Most customers with their own domain |
| Let's Encrypt IP certificate | Certificate issued directly for the IP address (short-lived ~6 days, automatic renewal via ACME) | Customers without a domain |
| Kestrel with PFX | HTTPS directly in the service: Kestrel:Endpoints:Https in appsettings.json with the path to the certificate |
Simple setups with an existing certificate |
Reverse proxy with Caddy
Caddy obtains and renews Let's Encrypt certificates fully automatically. A minimal
Caddyfile looks like: print.kunde.de { reverse_proxy localhost:5050 }.
A2 – Configure port forwarding
In your router/firewall, forward the public HTTPS port (e.g. 443) to the machine running MC.PrintService (port 5050, or the port used by the reverse proxy).
A3 – Restrict the firewall to Business Central
To prevent the forwarded port from being exposed to the entire internet, restrict inbound access
to the IP addresses used by Business Central. These are consolidated in the Azure service tag
Dynamics365BusinessCentral (maintained automatically by Microsoft).
- Firewalls with service tag support can use the tag directly.
- Otherwise, download the IP ranges as a JSON list and configure them as a firewall rule.
A4 – Create a printer in Business Central
- Search for Print Service Printers in BC → New.
- Fill in the fields:
| Field | Value |
|---|---|
| Code | Unique identifier, e.g. PDF-OFFICE or ZPL-WAREHOUSE1 |
| Description | Descriptive name |
| Service URL | Public HTTPS URL of the service, e.g. https://print.kunde.de (for local tests: http://localhost:5050) |
| Print Method | PDF (Windows printer) or ZPL (Zebra thermal printer) |
| Printer Name | For PDF: exact Windows printer name. For ZPL: IP address of the Zebra printer |
| ZPL Port | For ZPL only: TCP port (default 9100) |
- Enter the API key: Click the API Key field (DrillDown) → enter the key generated in Step 1. It is stored encrypted in the central Credential Store and is never displayed in plain text.
HTTPS required for remote URLs
The Service URL must use https://. http:// is only permitted for localhost/127.0.0.1
(local testing) and will be rejected for remote addresses.
A5 – Test the connection
Run the Test Connection action on the Printer Card. On success, the status changes to Online. Then print a test label (see Next steps).
| Error | Cause | Resolution |
|---|---|---|
| Status remains Offline | Service not reachable | Check port forwarding, reverse proxy, and service status |
| 401 Unauthorized | API key missing or incorrect | Compare the key in BC with the one in appsettings.json |
| Certificate error | Invalid or self-signed certificate | Use a valid certificate (Let's Encrypt) |
Part C: Setting up Relay (for customers without a fixed IP)
In Relay mode, the customer hosts the intermediary components in their own Azure. Business Central pushes the print job there; MC.PrintService in agent mode maintains only an outbound connection – no inbound port forwarding, no fixed IP, and no self-managed HTTPS certificate required. The print data flows exclusively through the customer's own Azure subscription, never through ALTENBRAND.
BC ──► Azure Function ──► Service Bus Queue (customer Azure)
│ │
│ Blob (label) │ AMQP (outbound 443)
▼ ▼
Blob Storage MC.PrintService (agent mode, on-premises) ──► Printer
The status return path runs actively from the Function back to Business Central (Managed Identity, no polling) – so in Relay mode a print job also moves from Sending to Printed or Failed.
Prerequisites for Relay
In addition to the general prerequisites above: a customer Azure subscription with permission to create resources and role assignments (role Owner or User Access Administrator), plus Azure CLI and Azure Functions Core Tools v4 on the machine running the deployment.
C1 – Provision the Azure infrastructure
The included Bicep/ARM template (azure/deploy/ in the Print Service repository) creates, in a
single step in the customer's subscription: Service Bus (Basic) + queue print-jobs, a Storage
Account + blob container labels, and a Function App (Consumption) with a system-assigned
Managed Identity plus the required role assignments.
Via Azure CLI:
az group create -n rg-mcps-relay -l westeurope
az deployment group create -g rg-mcps-relay \
--template-file azure/deploy/main.bicep \
--parameters \
namePrefix=mcpsrelay \
bcEnvironmentUrl="https://api.businesscentral.dynamics.com/v2.0/<tenantId>/<environment>" \
bcCompanyId="<company-systemid-guid>"
Alternatively, use the "Deploy to Azure" button in azure/README.md. From the deployment
outputs, note: functionBaseUrl, statusUrl, managedIdentityPrincipalId, and the Service
Bus name.
Finding the company SystemId
bcCompanyId is the SystemId of the BC company. It is available in BC under Companies
(field Id) or via the API …/api/v2.0/companies.
C2 – Publish the Function code
The template creates only the empty Function App. Publish the code once:
func azure functionapp publish <functionAppName> # name from the deployment output
C3 – Register the Managed Identity as a BC Application User
So that the Function may report print status back, its Managed Identity is registered as an application user in Business Central – the agent itself needs no BC access.
- In Microsoft Entra ID, open the enterprise application of the Function's Managed Identity
(object/principal id = output
managedIdentityPrincipalId, display name = Function App name) and note its Application (Client) Id. - In Business Central, search for Microsoft Entra Applications → New → enter the Client Id, State = Enabled.
- Assign the permission set
ALN MCPS Relay APIto the entry. It is deliberately minimal (status callback only), not full admin access.
S2S authentication
This is the only slightly more involved step. It follows the standard service-to-service pattern used throughout merchantCENTRAL (cf. OAuth2 in the hub).
C4 – Retrieve the keys
Function key (entered in BC on the printer):
az functionapp keys list -g rg-mcps-relay -n <functionAppName> --query "functionKeys.default" -o tsv
Service Bus listen SAS (entered in the agent configuration):
az servicebus queue authorization-rule keys list -g rg-mcps-relay \
--namespace-name <serviceBusName> --queue-name print-jobs --name AgentListen \
--query primaryConnectionString -o tsv
C5 – Create a printer in Business Central (transport Relay)
- Search for Print Service Printers in BC → New.
- Set Transport Mode to Relay (Azure Queue). The Direct Push fields (Service URL, API Key) are hidden and the Relay fields appear.
- Fill in the fields:
| Field | Value |
|---|---|
| Code / Description | as for Direct Push |
| Print Method | PDF (Windows printer) or ZPL (Zebra) |
| Printer Name | Windows printer name (PDF) or IP address (ZPL) – the agent prints locally with it |
| ZPL Port | ZPL only (default 9100) |
| Relay Function URL | functionBaseUrl from the deployment (e.g. https://mcpsrelay-func-....azurewebsites.net) |
| Relay Function Key | Click the DrillDown → enter the Function key from C4 (stored encrypted in the Credential Store) |
C6 – Start the agent (MC.PrintService) in agent mode
In the appsettings.json of the MC.PrintService installed in Step 1, fill in the Relay section
and restart the service:
"Relay": {
"Enabled": true,
"ServiceBusConnectionString": "<listen SAS from C4>",
"QueueName": "print-jobs",
"MaxDeliveryCount": 5,
"StatusCallbackUrl": "<statusUrl from C1>",
"StatusCallbackKey": "<Function key from C4>"
}
The agent runs alongside the HTTP listener – so a single instance can serve Direct Push and
Relay printers at the same time. The configuration UI (…/config) shows under "Relay Mode"
whether agent mode is active.
C7 – Test
Print a label to the relay printer in BC. Flow: job → Sending → the agent pulls it from the queue, prints, and reports back → Printed.
| Error | Cause | Resolution |
|---|---|---|
| Job stays Sending | Agent offline / wrong listen SAS | Check the agent service, Relay:Enabled, and the listen SAS; after MaxDeliveryCount attempts the job becomes Failed |
| 401 on enqueue | Wrong/missing Function key | Check the Function key on the printer (C5) |
| Status does not reach BC | Application user/permission missing | Check C3 (Entra application + permission set ALN MCPS Relay API) |
Costs (borne by the customer, in their Azure)
At 100–1,000 labels per day, costs are driven not by data volumes but by the pricing tier chosen: with Service Bus Basic, total costs are under ~€2/month.
→ Full setup guide with all outputs and troubleshooting: azure/README.md in the Print Service
repository. Architecture, authentication, and cost breakdown in detail:
docs/relay-mode-architecture.md.
Part D: Setting up Poll (BC stays passive)
The Poll mode uses the same Azure infrastructure and the same agent mode as Relay (Part C) - but the Function fetches the jobs from BC instead of BC pushing them. Business Central makes no outbound web service calls (except an optional tiny wake-ping). Ideal when BC must not be configured for outbound HTTP.
BC (passive) ◀──Poll/Status── Azure Function ──▶ Service Bus Queue
│ (opt.) Wake-ping ▲ │ Blob (label) │
└─────────────────────┘ ▼ ▼
Blob Storage MC.PrintService (agent) ──▶ Printer
Differences from Part C
Setup follows Part C (steps 1, 2, 4 and 6 are identical), with three differences:
| Step | For Poll |
|---|---|
| Application User (step 3) | Assign the permission set ALN MCPS Poll API (instead of ALN MCPS Relay API) - the Function also needs read access to the jobs here |
| Printer in BC (step 5) | Per printer, only Transport Mode = Poll + printer name. The Azure connection is configured once globally under Print Service Setup → Poll Connection (Function URL + Function key), not per printer |
| Wake-ping (optional) | Leave the poll Function URL empty in Setup to keep BC fully passive - the Function then polls only on its timer (default: every 5 minutes). With a URL/key configured, BC signals "there is work" and printing starts immediately |
Concurrency
A wake-ping and the timer can poll at the same time. A blob lease ensures only one poll run works at a time; additionally the Function claims each job with optimistic concurrency (ETag), so no job is ever printed twice.
Test
Print a label to the poll printer in BC. Flow: job → Sending (claimed by the Function) → the agent prints → Printed. Without a wake-ping it may take until the next timer run.
Troubleshooting
A detailed overview of the most common issues is available under MC.PrintService – Reference & Troubleshooting.
Quick check:
- Is the service running? → Windows Services (
services.msc) → "MC.PrintService" must show "Running". - Is it reachable? →
http://localhost:5050/healthin the browser → must returnHealthy. - Are printers visible? →
http://localhost:5050/printerslists all Windows printers. - Is the API key set? → The configuration UI at
…/configshows the status at the top.
Next steps
- Manage printers – printer list, status, fallback
- Routing rules – automatically route print jobs to the correct printer
- Print Subscriptions – automatic printing after posting operations
- Setup (reference) – all fields on the setup page
- MC.PrintService – Reference – endpoints, configuration, troubleshooting