ROKIConnect
Connectors Start API SDKs Migrate MCP Portal ES

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.

2 to 4 hours of actual work, split like this: 15 minutes to find out the version and whether Service Layer responds; 10 minutes to create the integration user; 10 minutes to assign it a license; 15 minutes for the minimum permissions; 20 minutes for the accountant to decide the accounting account; 30 minutes to export the certificate and request the two firewall rules; and the rest in the step 13 tests alongside the programmer. All of this gets done in one morning. These are NOT days.from the merchant
12 to 20 hours of programming, if Service Layer is already running. Honest breakdown: 1 hour for session handling (login, B1SESSION and ROUTEID cookies, retry on error 301); 2 to 3 hours to create the payment in ROKI and sort out the DocEntry versus DocNum mapping; 3 to 5 hours for the webhook receiver with HMAC signature verification over the raw body and idempotency; 4 to 6 hours for the POST of IncomingPayments with PaymentInvoices, which is where the hours really go because you have to fight SAP's accounting validations (a payment means that does not square with the applied amount, fields the localization requires that are not documented, date formats); 2 hours for the scheduled backup polling job; and 2 to 3 hours of serious testing. If Service Layer is NOT installed, add 2 to 4 hours from the implementation partner plus the maintenance window.of development
14 to 24 person-hours if Service Layer is already running. On the calendar: 2 to 4 business days, and those days are set by people's schedules, not by the work. A focused team gets it done in 2 days.in total

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:

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

1
SAP Business One 10.0 or higher. If the merchant runs on Microsoft SQL Server this is MANDATORY, non-negotiable. If it runs on SAP HANA, 9.3 or higher will do, but 10.0 is still preferable.

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.

2
Service Layer installed and running, listening on HTTPS port 50000 (the load balancer) and 50001, 50002, 50003... (the member nodes).

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.

3
Service Layer installed, if it is not. It is installed with the Components Setup Wizard, not with the main installer.

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.

4
A dedicated integration user inside the SAP company. Do NOT use the "manager" user, do NOT use the accountant's user, do NOT use the "Support" user.

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.

5
A license assigned to that integration user, and the contractual authorization for Indirect Access.

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.

6
Minimum permissions on the integration user. Do not give it superuser.

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.

7
The general-ledger account / bank account where the money coming in through ROKI will be credited.

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.

8
The EXACT name of the company database (the CompanyDB).

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.

9
TCP network access to port 50000 from the machine where the connector will run.

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+.

10
The Service Layer's TLS certificate, and the connector trusting it.

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.

11
Outbound internet access over HTTPS to aura.roki.systems from the connector machine.

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. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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."

  8. 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.

  9. 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.

  10. 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.

  11. 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.

  12. 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 1

    And 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.

  13. 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.

  14. 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.

  15. 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

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:

Sources