SAP Business One
SAP Business One 10.0 or higher, integrated through the Service Layer (REST/OData API on port 50000). Applies equally to both variants: Business One on Microsoft SQL Server and Business One on SAP HANA.
This is THE point you cared about, so I will say it without embellishment.
IF SERVICE LAYER IS ALREADY RUNNING, THIS IS HOURS. Not days, not weeks, not a project. A merchant who today opens https://su-servidor:50000 and sees the API reference load has practically all the infrastructure work done. What remains on their side is creating a user, assigning it a license, ticking off its permissions and choosing an accounting account. That is one morning of the accountant's work. And for HANA the probability is high: Service Layer has shipped with the platform since 9.x, and in most HANA installations it is already running because the Web Client depends on it.
WHERE THIS PROMISE BREAKS, three concrete scenarios:
1. Microsoft SQL Server on version 9.2 or 9.3. There is no Service Layer there and there is no shortcut. You have to upgrade to 10.0, and that is a 1 to 3 WEEK project for the implementation partner, with testing of add-ons, Crystal reports and customizations. When someone tells you the SAP integration took a month, it was almost always this and not the integration.
2. Service Layer not installed on a 10.0. Technically it is 2 to 4 hours of installation, but in practice what takes time is getting the maintenance window and the partner having room in their schedule. That can be a week of waiting with two hours of work inside it.
3. The certificate and the firewall. It sounds trivial and it is not: the Service Layer's self-signed certificate makes the connector reject the connection, and people lose half a day blaming the code before they realize. Budget for that half hour in step 6 seriously.
A COMPARISON TO CALIBRATE: people assume SAP is the hardest thing to integrate and that a cloud platform is easy. For this specific case it is the other way around. Service Layer is a well-documented OData API, with an official 224-page manual that includes real payload examples; finding the right object and the right payload is straightforward. The hard part of SAP is not the API: it is the BUREAUCRACY around it — the license, the partner, the maintenance window, the corporate firewall. If the merchant sorts out those four things for you in one day, you have the integration running in two.
WHAT IS a headache, said without marketing: SAP's accounting validations when creating the Incoming Payment. SAP will not let you post a document that does not square, and it is right, but the error messages are raw and sometimes the localization requires fields that are not in the general documentation (cash flow assignments, numbering series, dimensions). That is where the 6 hours of step 11 go, not the HTTP part. Budget for that and do not promise it will be done in an afternoon.
Who does it
No beating around the bush: the owner does NOT do this alone, and in most cases neither does the accountant alone.
THE OWNER: touches nothing technical. Their job is to decide three things and sign off on them: (1) which general-ledger bank account gets credited when a ROKI payment comes in, (2) whether to buy or free up a SAP license for the integration user, (3) whether or not to authorize opening a port in the office firewall. They are also the only one who can ask the implementation partner to do billable work.
THE ACCOUNTANT (if they are a SAP superuser): can do the inside-the-SAP-client part on their own, and that is quite a lot: create the integration user in "Administration → Setup → General → Users", assign it a license in "Administration → License → License Administration", give it permissions in "Administration → System Initialization → Authorizations → General Authorizations", and confirm the bank account in "Administration → Setup → Banking → House Bank Accounts". Careful: SAP's official documentation is explicit that only a user defined as a Superuser can enter the Authorizations submenu. If the accountant is not a superuser, they can't.
THE IMPLEMENTATION PARTNER (the SAP partner): is needed NO MATTER WHAT in these cases:
- If Service Layer is not installed. The installation is done with the Components Setup Wizard (install.exe in \Packages.x64\ComponentsWizard), it asks for the SLD site user password, it asks for a certificate, and in distributed installations the wizard has to be run on each machine. The accountant does not do that. And if the merchant is on SQL Server with a 9.x version, the partner has to do a full upgrade to 10.0 first — that is no longer hours, that is days.
- If the firewall, the TLS certificate, or DNS has to be touched.
- If the merchant is on SAP Business One Cloud hosted by the partner: there the merchant has no access to the server and EVERYTHING goes through the partner.
- To answer whether the merchant's license contract covers Indirect Access. That is a contractual matter, not a technical one.
THE PROGRAMMER: mandatory. There is no way around it. Service Layer is a raw OData API; it comes with no payment connector, no screens, no "charge button". Someone has to write the connector: login, session, create the payment in ROKI, receive the webhook, verify the signature, and post the Incoming Payment. That is not work for a SAP functional consultant.
Honest summary: the best arrangement is accountant + programmer, with the partner on standby. If Service Layer is already running, the partner barely participates. If it is not running, the partner is the bottleneck and the project slips from hours to weeks because of their calendar, not because of actual work.
What has to exist before you start
Where: In the SAP desktop client: "Help → About SAP Business One". The official License Guide cites that same menu. There you can read the version and the patch level.
Service Layer for the Microsoft SQL Server variant was only released with SAP Business One 10.0. On 9.2 and 9.3 over SQL Server it simply does not exist: there is no port 50000, there is nothing to call. If the merchant is on 9.x over SQL, this project does not start until the partner does the upgrade, and that is a separate project.
Where: Test 1, the simplest: open a browser ON THE SERVER or on a machine on the same network and go to "https://<servidor>:50000". The Administrator's Guide says verbatim that this is where you access the API help document: "you can access the API help document for the Service Layer in a Web browser from anywhere via this URL: https://<Load Balancer Address>:<Load Balancer Port>". If the API reference loads, Service Layer is alive. Test 2: "https://<servidor>:50000/balancer-manager", which the official guide names as the balancer manager for viewing the status of each member. Test 3: "https://<servidor>:50000/ServiceLayerController", the SAP Business One Service Layer Controller (it exists since 10.0 PL00 on HANA and 10.0 PL02 on Microsoft SQL). Test 4: in the SLD, "https://<servidor>:40000/ControlCenter", "Services" tab, look for a service of type Service Layer in the list.
This is the one prerequisite that defines whether this is hours or weeks. If all four tests respond, 80% of the work on the merchant's side is already done. The SLD's port 40000 is the same in both variants (SQL and HANA), so this test works for both.
Where: On the server: go to the installation folder, default path "\Packages.x64\ComponentsWizard", run "install.exe". In the wizard: "Setup Wizard" window → choose "Installation and Upgrade"; "Select Features" window → check "Service Layer"; "Network Address" window; "Service Port" window (port for SSO, default 40001); "Specify Security Certificate" window; "Landscape Server" window (asks for the landscape server address and the site user password); "Database Instances" window; "Service Layer" window → check "Install Service Layer Load Balancer and Port" (default port 50000), fill in "Service Layer Load Balancer Members" with "Starting Port" (default 50001) and "Node Count" (default 10); "Review Settings" window → "Install".
Documented prerequisite: Windows PowerShell 5 or higher on the machine. Also, remote installation is NOT supported: if the load balancer goes on server A and the members on B and C, the wizard has to be run on each server separately. This is done by the implementation partner, with a maintenance window. Budget for 2 to 4 hours and a services restart.
Where: "Administration → Setup → General → Users" (the "Users - Setup" window opens). Create a new user with a clear User Code like ROKICONNECT and a long, randomly generated password.
Traceability and damage control. Every Incoming Payment the connector creates will be signed with that user, and if something goes wrong you block that user without leaving the accountant with no system. Also, SAP licenses per named user and explicitly forbids sharing one user between people: using the accountant's user for an integration is exactly what the License Guide says you do not do.
Where: "Administration → License → License Administration", "Assignment" tab. There you see the license-type columns: Professional, Limited CRM, Limited Financials, Limited Logistics, Indirect Access, plus the "Total Assigned" and "Total Free" fields.
Here you have to be precise because there is a lot of confusion. The official SAP Business One 10.0 License Guide says: the Professional User, Limited CRM User, Limited Financials User, Limited Logistics User and Starter User types "all include Indirect Access authorizations". In other words: if the integration user has a Professional or Limited license assigned, API access is already covered contractually. There is also the standalone Indirect Access type for exactly this use. The technical side: to log in to Service Layer you only need a valid SAP Business One username and password, and in practice Service Layer does NOT enforce license verification on every call. That does not mean it is free: the license is a contractual obligation, not a technical lock. Ask the implementation partner whether the merchant's contract already includes Indirect Access or whether it has to be bought. If it has to be bought, that is money and it is the owner's decision.
Where: "Administration → System Initialization → Authorizations → General Authorizations". On the left side you pick the ROKICONNECT user, and in the tree on the right you give full authorization (Full Authorization) ONLY to the branch of the Banking module that corresponds to Incoming Payments, and read access (Read Only) to the Sales - A/R branch for A/R invoices. Everything else: No Authorization.
The connector only needs to do two things: READ an A/R invoice and CREATE an incoming payment. Nothing else. With that, even if the connector's password gets stolen, nobody can create items, touch prices, see payroll or delete customers. HONEST WARNING: the menu path is confirmed in SAP's official documentation, but the exact names of the leaves of the authorizations tree (the lines inside Banking and Sales - A/R) vary by localization and by version, and I did not find them written verbatim in the manuals I read. Have the accountant confirm them on screen instead of someone making them up. Remember also that only a Superuser can enter this submenu.
Where: "Administration → Setup → Banking → House Bank Accounts" ("House Bank Accounts - Setup" window).
The Incoming Payment cannot be created out of thin air: you have to tell SAP which account the money comes in against. The connector will send that account code in the TransferAccount field (the official API examples use values like "_SYS00000000004"). This is the ACCOUNTANT's decision, not the programmer's: normally you open a clearing account like "ROKI Connect pending settlement", because the money has not yet landed in the physical bank on the day the customer pays. If this is not defined properly, bank reconciliation becomes hell the following month.
Where: In the "Choose Company" screen of the SAP client, in the database-name column. It is something like SBODEMOHN or whatever name the implementer gave it.
The Service Layer login asks for three things: CompanyDB, UserName and Password. If the CompanyDB is misspelled, even by one capital letter, the login fails and the message is not always clear. Write it down exactly as it is, respecting capitalization.
Where: Internal office firewall rule. From the connector machine: test that "https://<servidor>:50000" opens.
Watch out for the security detail SAP's own guide brings up: traffic between the load balancer and the members goes over HTTP, not HTTPS, and SAP recommends configuring each member machine's firewall to ONLY accept visits from the load balancer. In other words: the connector talks to 50000, never to 50001+.
Where: It is defined during installation in the "Specify Security Certificate" window, where the installer also allows "use a self-signed certificate".
Service Layer enforces HTTPS with TLS 1.2 or 1.3. Most installations were left with a self-signed certificate. A normal HTTP client will reject that connection. You have to export that certificate and install it in the trust store of the connector machine. Disabling certificate verification is the easy way out and it is a bad idea: it exposes you to anyone on the internal network impersonating the SAP server.
Where: Outbound firewall rule.
The connector has to be able to POST to https://aura.roki.systems/api/connect/v1/payments and GET /payments/{id}. Many office networks block arbitrary outbound traffic from servers. It is one firewall line, but if it is not requested in advance, it gets discovered on test day and the afternoon is lost.
The steps
- 1. Find out where you stand (15 minutes, done by the accountant)
Open the SAP client and go to "Help → About SAP Business One". Write down the version and the patch level. Then open a browser on a machine on the office network and go to "https://<direccion-del-servidor>:50000".
If a page loads with the Service Layer API reference: congratulations, you already have the hard part. Continue to step 2.
If nothing loads and the version is 10.0 or higher: Service Layer is not installed. Go to step 1-bis.
If the version says 9.2 or 9.3 and the database is Microsoft SQL Server: stop everything. No Service Layer is possible. You have to upgrade to 10.0 first and that is a separate project with the implementation partner. This guide does not apply until that is finished.
If the version says 9.3 and the database is SAP HANA: you probably do have Service Layer, keep testing.Second check, the SLD one: go to "https://<direccion-del-servidor>:40000/ControlCenter", "Services" tab. If a service of type Service Layer appears in that list, it is registered correctly. Port 40000 is the same on SQL Server and on HANA.
- 1-bis. Install Service Layer, only if it is missing (2 to 4 hours, done by the implementation partner)
This is NOT done by the owner or the accountant. It requires administrator access to the server, the SLD site user password, and a maintenance window because services get restarted.
The partner runs "install.exe" from "\Packages.x64\ComponentsWizard". Documented prerequisite: Windows PowerShell 5 or higher installed on that machine.
The window sequence is: "Setup Wizard" → choose "Installation and Upgrade". "Select Features" → check "Service Layer". "Network Address" → choose IP or hostname (it comes pre-filled with the FQDN). "Service Port" → port for SSO, default 40001. "Specify Security Certificate" → own certificate or self-signed. "Landscape Server" → address and site user password. "Database Instances" → choose the instance if there are several. "Service Layer" → check "Install Service Layer Load Balancer and Port" with port 50000, and fill in "Service Layer Load Balancer Members" with "Starting Port" 50001 and "Node Count" (default 10; for a small merchant 3 or 5 is more than enough). "Review Settings" → "Install".
A detail that gets forgotten: remote installation is not supported. If the load balancer goes on one server and the members on others, the wizard has to be run machine by machine.
When finished, verify at "https://<servidor>:50000/balancer-manager" that the members show up with status Ok.
- 2. Create the integration user (10 minutes, done by the accountant if they are a superuser)
In the SAP client: "Administration → Setup → General → Users". The "Users - Setup" window opens.
Create a new user. Suggested User Code: ROKICONNECT. Password: randomly generated, long, stored in a password manager, never in an email or a WhatsApp message.
Do NOT check the Superuser box. Do NOT reuse "manager". Do NOT use the "Support" user (that one is reserved for partner support, comes locked by default and only works if the remote support platform uploaded a report in the last 7 days).
Also write down the exact name of the company database, which you will see in the "Choose Company" screen. That is the CompanyDB for the login.
- 3. Assign it a license (10 minutes, done by the accountant; the buying decision is the owner's)
"Administration → License → License Administration", "Assignment" tab.
Find the ROKICONNECT user and assign it a license. Look at the "Total Assigned" and "Total Free" columns to know whether there is a free seat.
Which license: if the merchant has a free Professional or Limited seat, that is enough — SAP's License Guide says those types already include the Indirect Access authorizations. If there is no free seat, one has to be bought, and there the owner decides.
The technical versus the contractual, said plainly: technically Service Layer will let you log in with a valid user even if the license situation is shaky, because it does not enforce the verification on every call. Contractually, SAP requires the Indirect Access authorization. Do not play the "it works anyway" game: ask the implementation partner whether the contract already covers it. A SAP audit is not a good day.
- 4. Give it minimum permissions (15 minutes, done by the accountant if they are a superuser)
"Administration → System Initialization → Authorizations → General Authorizations".
In the list on the left select ROKICONNECT. In the tree on the right:
- Banking module branch, the Incoming Payments part: Full Authorization.
- Sales - A/R module branch, the A/R Invoice part: Read Only (the connector only needs to read the invoice, never modify it).
- All the rest of the tree: No Authorization.Honest warning: the menu path is confirmed word for word in SAP's official documentation, but the exact names of each leaf of the tree change depending on localization and version, and I did not find them written verbatim in the manuals I read. Confirm them on screen, do not make them up.
Remember that only a user defined as a Superuser can enter this submenu.
- 5. Define the accounting account for the payment (20 minutes, decided by the accountant)
"Administration → Setup → Banking → House Bank Accounts" ("House Bank Accounts - Setup" window).
Decide which account the ROKI money comes in against. Write down the general-ledger account code that screen gives back; the connector will use it in the TransferAccount field.
An accounting recommendation, not a technical one: use a clearing account like "ROKI Connect pending settlement" instead of the real bank account. The customer pays today, but the money lands in the bank account days later. If you post directly against the bank, the month's bank reconciliation will never square and you will blame the system.
- 6. Export the certificate and open the network rules (30 minutes, implementation partner or whoever manages the network)
Export the Service Layer's TLS certificate and install it in the trust store of the machine where the connector will run. If it is self-signed (the most common case), without this no serious HTTP library will connect.
Firewall rules that have to be requested:
- From the connector machine to the SAP server: TCP 50000 (only the load balancer, never 50001+; SAP's own guide recommends that the members only accept traffic from the load balancer).
- From the connector machine to the internet: HTTPS to aura.roki.systems.Do NOT open 50000 to the internet. SAP's Administrator's Guide says it literally, and in both variants: "The Service Layer is for internal component calls only and you do not need to expose it to the Internet."
- 7. Test the login by hand before writing a single line of code (10 minutes, done by the programmer)
With Postman or curl, from the connector machine:
POST https://<servidor>:50000/b1s/v1/Login
{"CompanyDB": "SBODEMOHN", "UserName": "ROKICONNECT", "Password": "..."}Expected response, 200 OK, with two cookies in the header and a body like this:
Set-Cookie: B1SESSION=PTRzIjYK-weN6-1Lx1-ZG0J-3ARxfjcU0Shy;HttpOnly;
Set-Cookie: ROUTEID=.node1; path=/b1s
{ "SessionId": "...", "Version": "1000110", "SessionTimeout": 30 }If that responds, EVERYTHING else is normal programming. If it does not respond, the problem is in steps 1 to 6 and there is no point moving forward.
Two versions of the API coexist: /b1s/v1 is OData v3 and /b1s/v2 is OData v4. Use v2 in new projects: as of SAP Business One FP 2405, OData v3 is deprecated. The Login endpoint works in both.
- 8. Program the session handling (1 hour, done by the programmer)
The session opens with Login and closes with Logout. The "SessionTimeout": 30 field means 30 minutes of inactivity.
Rules the connector has to respect:
- Send the B1SESSION cookie on ALL requests. It is mandatory. The documentation is explicit: if your application does not run inside a browser, you have to set the cookie by hand on every request.
- Send ROUTEID too. It is optional but it keeps session stickiness to the same load balancer node, and without it you will see weird, intermittent errors when there are several nodes.
- If the session expired, Service Layer returns 401 Unauthorized with this exact body: { "error": { "code": 301, "message": { "lang": "en-us", "value": "Invalid session or session already timeout." } } }. On seeing that, log in again and retry the operation ONCE.
- Do not open a session for every payment. Keep one session alive and renew it.
- Do a Logout when the connector shuts down. Dangling sessions consume server resources.If the timeout needs raising, it is changed with the SessionTimeout parameter in the conf/b1s.conf configuration file, or from the Service Layer Controller interface at "https://<servidor>:<puerto>/ServiceLayerController", Service Layer Settings tab. Note for Windows servers: before using the Controller you have to verify with Get-ExecutionPolicy that the PowerShell execution policy is set to "Unrestricted", and if it is not, set it with Set-ExecutionPolicy Unrestricted.
- 9. Create the payment in ROKI and send the link to the customer (2 to 3 hours, done by the programmer)
When the merchant issues an A/R invoice in SAP and wants to collect it, the connector does:
POST https://aura.roki.systems/api/connect/v1/payments
Authorization: Bearer sk_test_... (sk_live_... in production)
{ "amount": 1500.00, "external_reference": "FAC-001", "name": "Factura 001" }The amount goes in lempiras with decimals. 1500.00, not 150000. ROKI responds 201 with { id, checkout_url, transaction_id: null }.
In external_reference send something you can find again in SAP. Here is the most important trap of this whole integration: SAP has TWO document numbers and they are not the same.
- DocEntry: the document's internal key. It is what the API calls by URL: GET /b1s/v2/Invoices(123). The official documentation says it: "DocEntry is the key property".
- DocNum: the number the human sees on the printed invoice.
It is perfectly normal for invoice number 1 to have DocEntry 22 and DocNum 11. SAP's official documentation includes that exact example.Recommendation: send the DocNum in external_reference (which is what the customer recognizes on their invoice) but STORE the DocEntry separately, because the DocEntry is what you will need to apply the payment. If you send DocNum and later try to apply the payment using that number as if it were DocEntry, you will credit money against the wrong invoice. Silently. With no error.
You send the checkout_url to the customer by email or WhatsApp, or put it on the invoice PDF as a QR code.
- 10. Receive the ROKI webhook (3 to 5 hours, done by the programmer; see the webhook section for the firewall problem)
ROKI sends a POST to a public HTTPS URL with the header:
ROKI-Signature: t=<unix>,v1=<hmac_sha256_hex>The signature is HMAC SHA-256 over timestamp + "." + the RAW body. Raw means the bytes exactly as they arrived, before parsing the JSON. If your framework gives you the body already deserialized and you re-serialize it, the signature will never match and you will lose half a day looking for the bug in the wrong place.
What the receiver has to do, in order:
1. Read the raw body.
2. Recompute the HMAC and compare it in constant time.
3. Verify that the timestamp is not old (reject anything older than 5 minutes, to cut off replays).
4. Only then parse the JSON.
5. If the event is payment.approved, queue the recording in SAP.
6. Respond 200 fast. Do not do the heavy work inside the webhook handler.Idempotency, mandatory: store ROKI's transaction_id in your local database before touching SAP. If the same webhook arrives twice (and it will), the second time must not create a second Incoming Payment. Duplicating payments in a client's accounting is one of the most expensive mistakes you can make.
- 11. Record the payment in SAP: the Incoming Payment against the A/R invoice (4 to 6 hours, done by the programmer)
This is the heart of the integration. The exact Service Layer business object is IncomingPayments. The official API reference describes it like this: "This entity enables you to manipulate 'IncomingPayments'. It represents incoming payments from customers or, for returned goods, from vendors." The payload is of type 'Payment'. The A/R invoice is the Invoices entity, payload type 'Document'. Under the hood, IncomingPayments is stored in the ORCT table (VendorPayments, which is the vendor-payments equivalent, goes in OVPM — do not confuse them).
The call:
POST https://<servidor>:50000/b1s/v2/IncomingPayments
Cookie: B1SESSION=...; ROUTEID=.node1
{
"CardCode": "C001",
"DocDate": "2026-08-16",
"DocCurrency": "HNL",
"TransferAccount": "_SYS00000000004",
"TransferDate": "2026-08-16",
"TransferSum": 1500.00,
"Remarks": "ROKI Connect - pago <id> - trx <transaction_id>",
"PaymentInvoices": [
{ "DocEntry": 4711, "InvoiceType": "it_Invoice", "SumApplied": 1500.00 }
]
}What each thing is:
- CardCode: the customer's (business partner's) code in SAP.
- TransferAccount / TransferDate / TransferSum: the payment means. The official API examples use exactly these three names for a bank transfer, with the account in the "_SYS00000000004" format. For cash the fields would be CashAccount and CashSum.
- PaymentInvoices: the collection that TIES the payment to the invoice. This is the correct name in Service Layer; the official name-equivalence appendix confirms that PaymentInvoices in Service Layer is Payments_Invoices in the DI API.
- DocEntry inside PaymentInvoices: the invoice's internal key, NOT the DocNum.
- SumApplied: how much of that payment is applied to that invoice.
- Remarks: free-text field, confirmed in the official examples. Put the ROKI id and transaction_id in there so the accountant can trace any journal entry back to the original payment.TWO HONEST WARNINGS about this payload:
1. The "it_Invoice" value for InvoiceType appears in SAP community examples and in the DI API, but I did NOT find it written with that exact name in the official Service Layer API Reference I read. Confirm it against the $metadata of the real installation before taking it as good.
2. There is a TransferReference field that is commonly used to store the payment's external reference, but I could not confirm it in the official reference I read either. That is why I used Remarks above, which IS confirmed.Looking up the DocEntry from the invoice number, if you did not store it earlier:
GET https://<servidor>:50000/b1s/v2/Invoices?$select=DocEntry,DocNum,DocTotal,CardCode&$filter=DocNum eq 1And to void a payment (for example if ROKI does a reversal): it is NOT deleted. IncomingPayments does not accept DELETE. It is voided with the Cancel action:
POST https://<servidor>:50000/b1s/v2/IncomingPayments(123)/Cancel
That generates an accounting cancellation document, which is the correct thing. There is also CancelbyCurrentSystemDate if you need the cancellation to carry today's date instead of the original document's date. - 12. Program the polling backup, which is what actually saves you (2 hours, done by the programmer)
Webhooks get lost. The server restarts, the network goes down, the firewall blips. You need a second path, and in the case of an in-office SAP this second path is often the ONLY path (see the webhook section).
A scheduled job that runs every 5 or 10 minutes:
1. Reads from your local database all the payments you created in ROKI and have not yet marked as recorded in SAP.
2. For each one: GET https://aura.roki.systems/api/connect/v1/payments/{id} with the Bearer.
3. If the status indicates approved and you have not recorded it yet, run exactly the same logic as step 11.
4. Mark it as recorded using the transaction_id as the idempotency key, the same one the webhook uses.The key is that the webhook path and the polling path end in the SAME idempotent function. That way it does not matter which of the two arrives first: the result is a single Incoming Payment in SAP.
- 13. Test in sandbox against a test company (2 to 3 hours, programmer and accountant together)
Use ROKI's sk_test_ key and a SAP test database (never the production one).
Cases that must be tested no matter what:
- Full payment of an invoice. Verify in SAP that the invoice ends up closed and the customer's balance goes down.
- Partial payment. Verify that the invoice stays open with the correct balance.
- The same webhook arriving twice. It must create ONE single Incoming Payment.
- A webhook with an invalid signature. It must be rejected with 400 and be recorded in the log.
- Session expiring mid-operation. The connector must re-login on its own and complete.
- Service Layer down. The connector must retry, not lose the payment.
- Voiding with POST IncomingPayments(id)/Cancel.Have the ACCOUNTANT look at the resulting journal entries in SAP and say whether they are right. The programmer cannot judge that. This step is the one most often skipped and the most expensive one to skip.
- 14. Go to production (1 hour)
Change sk_test_ to sk_live_. Point to the real database. Confirm that the accounting account from step 5 is the definitive one and not the test one.
Run ONE real payment for a small amount, end to end, and have the accountant verify it in SAP before announcing anything to customers.
Leave the connector's log accessible: when something fails three months from now, someone is going to need to see what SAP responded, and Service Layer's error messages are raw but informative.
How the webhook arrives
SHORT ANSWER: SAP Business One CANNOT receive the ROKI webhook. Not installed in the office, not installed in the cloud. Ever. And the problem is not just the firewall.
There are two reasons, and the second one is the one that matters:
REASON 1, the firewall. A typical SAP Business One sits on a server inside the office, behind the merchant's router, with a private IP. The internet cannot reach it. You already knew that.
REASON 2, the one almost nobody mentions: even if you opened the firewall wide, it would NOT HELP. Service Layer is an OData API for manipulating business objects (invoices, payments, items). It does not have, and never will have, an endpoint that accepts an arbitrary POST from a third party in a foreign format. There is no "/webhooks/roki" where ROKI can write. Service Layer does not know how to verify a ROKI-Signature header or interpret a payment.approved event. It is a database API with business rules, not an event receiver.
And on top of that, SAP explicitly forbids it. The official Administrator's Guides, both the Microsoft SQL Server one and the SAP HANA one, say verbatim in their ports appendix: "The service layer is for internal component calls only and you do not need to expose it to the Internet." Exposing 50000 to the internet is not an aggressive configuration: it is going against the vendor's written recommendation, with an API that gives access to the company's entire accounting.
WHAT IS DONE INSTEAD: a CONNECTOR. A small service, written by you, that stands in the middle. ROKI talks to the connector; the connector talks to SAP. SAP and ROKI never see each other's faces.
There are four ways to set up that connector. In the order I would recommend:
OPTION A — CONNECTOR INSIDE, NO WEBHOOK, BY POLLING. The recommended one for 80% of merchants. The connector runs on a machine inside the office network, the same network as the SAP server. It does NOT receive the webhook. Instead it polls every 5 minutes: GET https://aura.roki.systems/api/connect/v1/payments/{id} for all the payments it has pending. Advantages: ZERO inbound firewall changes. Zero open ports. Zero public DNS. Zero public certificate. Zero new attack surface. The merchant's network team does not have to approve anything, because it only uses outbound HTTPS that is already allowed. Disadvantage: there is up to a 5-minute delay between the customer paying and the payment showing up in SAP. For collecting invoices, 5 minutes does not matter to anyone. If it does matter, lower the interval to 1 minute. This is the option I recommend to any merchant that does not have a full-time system administrator. It is boring, it is cheap, and it does not break.
OPTION B — CONNECTOR INSIDE, WITH AN OUTBOUND TUNNEL. If they really want the instant webhook. The connector runs inside just like in option A, but it opens an outbound tunnel to a public service (Cloudflare Tunnel, ngrok, or equivalent). That service gives you a public HTTPS URL that points at the connector, and traffic comes in over a connection the connector itself initiated outward. Advantage: real-time webhook without opening a single inbound port in the firewall. Disadvantage: you add a dependency on a third party. If the tunnel goes down, you stop receiving. That is why: even if you use this option, KEEP the option A polling RUNNING anyway as a backup.
OPTION C — CONNECTOR INSIDE, WITH AN OPEN PORT. Only if the merchant already has serious infrastructure. The connector is published to the internet over HTTPS 443, with its own domain name and a certificate from a recognized authority, typically behind a reverse proxy. SAP's Administrator's Guide describes exactly those two external-access scenarios in its "Preparing External Addresses" section: "Reverse proxy (recommended)" and "NAT/PAT". Non-negotiable rule: what gets published is the CONNECTOR, on 443. Service Layer's port 50000 stays inside, always. Disadvantage: now you have a service exposed to the internet inside the office network, and that has to be maintained, patched and monitored. Many small merchants are not up for that.
OPTION D — CONNECTOR OUTSIDE, WITH A VPN TO THE OFFICE. The connector lives in the cloud and receives the webhook comfortably, but then it has to reach port 50000 which is inside, and that demands a site-to-site VPN to the office. It is the most expensive option and the one with the most moving parts. It only makes sense if the merchant ALREADY has a VPN running for other reasons. If they don't have one, do not build a VPN just for this.
A NEW AND RELEVANT FACT, BUT IN THE OPPOSITE DIRECTION: as of SAP Business One 10.0 FP 2602, Service Layer DOES support webhooks of its own. The documentation says it literally: "As of SAP Business One 10.0 FP 2602, the Service Layer supports webhooks." They are managed with the EventSubscriptions entity, they are enabled per company by calling CompanyService_UpdateAdminInfo with {"AdminInfo": {"EnableWebhook": "tYES"}}, and they require a new component called Webhook Messenger Service. They support HMAC SHA-256 authentication, same as ROKI. BUT CAREFUL, do not get the direction confused: that is SAP SENDING webhooks outward, not receiving them. It works for the opposite: so SAP can notify your connector when an invoice is created, and the connector can generate the ROKI payment link automatically without having to keep polling SAP. It is genuinely useful for step 9, but it does NOT solve the problem of receiving the payment from ROKI. Also, FP 2602 is from February 2026: almost no merchant has it installed yet. Do not build the design assuming it is available.
PRACTICAL CONCLUSION: write the connector with the payment-recording logic in ONE single idempotent function, with ROKI's transaction_id as the key. Then attach two triggers to it: the webhook (if you can receive it) and the periodic polling (always). That way the same code works for a merchant with a public IP and for one behind a home router, and you can switch options without rewriting anything.
What breaks
THE PORT MYTH, corrected: I checked both official manuals and the Service Layer's default port does NOT change between SQL Server and HANA. Both say exactly the same thing: 50000 for the load balancer and 50001, 50002, 50003... for the members. Your starting point was even stronger than you believed. If someone tells you it is a different port on HANA, they are either thinking of the database port (3xx13/3xx15 on HANA versus 1433 on SQL Server, which the connector does not care about) or it was changed by hand in that installation.
THE REAL DIFFERENCE BETWEEN SQL AND HANA IS NOT THE PORT, IT IS THE MINIMUM VERSION: Service Layer for Microsoft SQL Server has only existed since SAP Business One 10.0. A merchant on SQL Server at 9.2 or 9.3 has no port 50000 and never will without a full upgrade. On HANA it has existed since 9.x. If you are going to ask ONE single thing before quoting, ask the version, not the database.
DocEntry IS NOT DocNum. It is the most expensive trap in this integration. DocEntry is the internal key the API uses to identify the document; DocNum is the number the human sees on the printed invoice. SAP's official documentation includes an example where the same document has DocEntry 22 and DocNum 11. If you apply a payment using the DocNum as if it were DocEntry, you will credit the money against ANOTHER invoice, belonging to ANOTHER customer, with no error message whatsoever. Always store the DocEntry, and if you only have the DocNum, look it up with $filter=DocNum eq N before applying anything.
THE SESSION DIES AT 30 MINUTES. The SessionTimeout field in the login response is 30 by default. When it expires, Service Layer returns 401 with code 301 and the message "Invalid session or session already timeout." The connector has to detect that code, re-login and retry. If you do not program it, the integration works perfectly in the afternoon tests and falls over on its own during the night.
THE B1SESSION COOKIE IS MANDATORY ON EVERY CALL, and if your application does not run inside a browser you have to set it by hand. The documentation warns about this explicitly. Many people do the login, see the 200 OK, and then wonder why the next call returns 401.
THE ROUTEID COOKIE LOOKS OPTIONAL AND IN PRACTICE IT IS NOT. It is the one that keeps stickiness to the same load balancer node. If the installation has the default of 10 nodes and you do not send it, you will see intermittent errors that are impossible to reproduce: one call works, the next one does not.
THE RAW WEBHOOK BODY. The ROKI-Signature signature is an HMAC over timestamp + "." + the exact bytes of the body. If your framework parses the JSON and you re-serialize it to compute the signature, one space of difference and it no longer matches. Capture the raw body BEFORE the framework touches it. It is the number one cause of "the signature won't validate for me".
IDEMPOTENCY OR DUPLICATE PAYMENTS. Webhooks get resent. If your handler creates an Incoming Payment every time a message arrives, a single resend credits the customer double. Store the transaction_id in your database BEFORE touching SAP and use it as a unique key. And make the webhook and the backup polling end in the SAME idempotent function.
THE SELF-SIGNED CERTIFICATE. Service Layer enforces HTTPS with TLS 1.2 or 1.3, and most installations were left with the self-signed certificate the installer offers in the "Specify Security Certificate" window. Your connector will reject it. The correct solution is to export that certificate and install it in the trust store of the connector machine. The easy solution is to disable verification, and it is a bad idea: on the internal network anyone can impersonate the SAP server and walk away with the credentials of a user that can create journal entries.
DO NOT EXPOSE 50000 TO THE INTERNET. Both Administrator's Guides say it verbatim: "The service layer is for internal component calls only and you do not need to expose it to the Internet." It is an API with access to the entire accounting. What gets published, if anything, is the connector on 443. Never Service Layer.
THE NODES FROM 50001 ONWARD TALK HTTP, NOT HTTPS. The official guide recommends configuring each member machine's firewall so it only accepts traffic from the load balancer. Your connector always hits 50000, never a node directly.
SAP'S WEBHOOKS GO IN THE OPPOSITE DIRECTION. As of SAP Business One 10.0 FP 2602 Service Layer supports webhooks with the EventSubscriptions entity, but that is SAP NOTIFYING outward when something happens inside. It is not SAP receiving. It works so your connector finds out an invoice was created and generates the payment link on its own, and that is genuinely useful. It does not work for receiving the payment from ROKI. And besides, FP 2602 is from February 2026: almost nobody has it installed, do not design assuming it.
INCOMINGPAYMENTS CANNOT BE DELETED. The official API reference exposes GET, POST, PATCH and the actions Cancel, CancelbyCurrentSystemDate, GetApprovalTemplates and RequestApproveCancellation. There is no DELETE. To reverse a payment you use POST IncomingPayments(id)/Cancel, which generates the accounting cancellation document. It is right that it works this way: accounting is not deleted, it is counter-posted.
THE PAYMENT MEANS HAS TO SQUARE WITH WHAT IS APPLIED. If you send TransferSum 1500.00 but apply 1200.00 in PaymentInvoices, SAP rejects the document. It sounds obvious and it is where the most time gets lost in testing, above all with partial payments, early-payment discounts and rounding.
THE OFFICIAL EXAMPLES INCLUDE CashFlowAssignments AND IT IS NOT ALWAYS OPTIONAL. Both the IncomingPayments example and the VendorPayments example in the official reference include it. Depending on the company configuration and the localization, the POST can fail without that collection. If you get a cash flow error, that is where to look.
TWO THINGS I COULD NOT CONFIRM AND WILL NOT INVENT FOR YOU: (1) the "it_Invoice" value for the InvoiceType field appears in SAP community examples and in the DI API, but I did not see it written with that exact name in the official Service Layer API Reference I read; (2) the TransferReference field, which many people use to store the payment's external reference, I could not confirm in that reference either. Verify both against the $metadata of the real installation (GET /b1s/v2/$metadata) before taking them as good. In the meantime use Remarks, which IS confirmed in the official examples.
THE LICENSE: TECHNICALLY PERMISSIVE, CONTRACTUALLY NOT. Service Layer lets you log in with any valid user and in practice does not enforce license verification on every call. That does NOT mean the integration is free. SAP's License Guide says that the Professional, Limited CRM, Limited Financials, Limited Logistics and Starter types include the Indirect Access authorizations, and that the model is named-user with an express prohibition on sharing users. Have the partner confirm the contract. Do not hide behind the fact that it works.
FORGETTING THE POLLING BACKUP. If you only program the webhook, the day the network goes down or the server restarts you will have customers who paid and invoices that are still open in SAP, and nobody will find out until an angry customer calls. The periodic GET /payments/{id} is cheap to program and it is what turns this into a reliable system. For an in-office SAP, moreover, it is often the only possible path.
THE SERVICE LAYER CONTROLLER ON WINDOWS DEMANDS POWERSHELL SET TO "Unrestricted". The documentation is explicit: before using the controller on the Microsoft SQL version you have to check with Get-ExecutionPolicy and, if it is not at "Unrestricted", set it with Set-ExecutionPolicy Unrestricted. If the administrator does not know this, they will think the controller is broken.
THE CONTROLLER DOES NOT MANAGE DISTRIBUTED INSTALLATIONS. The limitation is written in the manual: if some nodes are on one machine and the load balancer on another, the Service Layer Controller does not manage them. It applies equally on HANA and on SQL. In those cases you have to go to each server by hand.
USE /b1s/v2 IN NEW PROJECTS. /b1s/v1 is OData v3 and has been deprecated since SAP Business One FP 2405; /b1s/v2 is OData v4 and is the main protocol. The Login endpoint works in both, so it is easy to stay on v1 out of inertia. Do not do it.
DO NOT USE THE "manager" USER OR THE "Support" USER. The first one has full privileges and if the connector's password gets stolen you lose everything. The second one is reserved for partner support: it comes locked by default, only works if the remote support platform uploaded a report in the last 7 days, and everything it does gets logged with name and reason. Neither of the two is for an integration.
What we could not confirm
Every menu name and port on this page was checked against the platform's official documentation. These 6 points could not be confirmed, and are flagged rather than presented as facts:
- "Service Port" window of the Components Setup Wizard: "port for SSO, default 40001" - different-name. The window is called exactly "Service Port" (confirmed), but the default value is no longer 40001. In the Administrator's Guide 10.0 PL02 (v1.2.1, 2020-07-01) it said 40001; in the current guides (SQL
- "The connector will send that account code in the TransferAccount field (the official API examples use values like \"_SYS00000000004\")" - different-name. The TransferAccount field and the _SYS00000000004 value DO appear in the official Service Layer reference, but in the POST /b1s/v2/VendorPayments example (payments made to vendors), not in the incomin
- "The connector will send that account code [the one defined in House Bank Accounts - Setup] in the TransferAccount field" - not-verifiable. No official documentation I was able to read connects the code from the "House Bank Accounts - Setup" window with the API's TransferAccount field. TransferAccount takes values in the _SYS00000000004 f
- (Preamble) "Both Administrator's Guides say exactly the same thing in their Appendix 1: List of Default Ports for Different Server Components" - different-name. The table content is identical word for word in the two variants: "50000 (for load balancer); 50001, 50002, 50003… (for load balancer members) | Service Layer | The service layer is for internal compo
- "to log in to Service Layer you only need a valid username and password" and "in practice Service Layer does NOT enforce license verification on every call" - not-verifiable. The login IS documented (CompanyDB + UserName + Password). But the claim that the Service Layer does not validate the license per call does not appear in any official guide I was able to read, neither
- In the "Choose Company" screen of the client, in the database-name column - not-verifiable. The "Choose Company" window exists and is cited in the Administrator's Guides ("the Choose Company window displays only the companies to which your Windows account is bound"; "Manually enter the compa
Sources
- https://help.sap.com/doc/fc2f5477516c404c8bf9ad1315a17238/10.0/en-US/Working_with_SAP_Business_One_Service_Lay
- https://help.sap.com/doc/3ec041c030424a96b54cfbe7192bfcb5/10.0/en-US/AdministratorGuide_SQL.pdf — "Administrat
- https://help.sap.com/doc/4e7c047f2c9e4cbe97800ffaf7b68f8e/10.0/en-US/B1_for_SAP_HANA_Admin_Guide.pdf — "Admini
- https://help.sap.com/doc/056f69366b5345a386bb8149f1700c19/10.0/en-US/Service%20Layer%20API%20Reference.html —
- https://help.sap.com/doc/14c6d92982fd4a9dbf7df66b5108433b/10.0/en-US/LicenseGuide.pdf — "License Guide for SAP
- https://help.sap.com/doc/089315d8d0f8475a9fc84fb919b501a3/10.0/en-US/SDKHelp/SAPbobsCOM~Payments.html — "API R
- https://help.sap.com/docs/SAP_BUSINESS_ONE/68a2e87fb29941b5bf959a184d9c6727/4506b88b7d720487e10000000a155369.h
- https://help.sap.com/docs/SAP_BUSINESS_ONE/68a2e87fb29941b5bf959a184d9c6727/45071a5bf61941dee10000000a1553f6.h
- https://help.sap.com/docs/SAP_BUSINESS_ONE/f110a154dd0f4c20bf7f3ebca9eeb794/84d62151fbf64b69aec096431539f839.h
- NOTA SOBRE FUENTES NO OFICIALES: dos datos que circulan mucho NO los pude confirmar en la documentacion oficia
