Contents
- Which Notice, Which Channel?
- Architecture: Direct SOAP from ABAP
- Credentials and Workplace Registry Numbers
- The Hiring Notice
- The Termination Notice
- Timeouts and Duplicate Notices
- Vizite: Fetching and Confirming Sick-Leave Reports
- MUHSGK: The Return Goes to the Revenue Administration
- Code Tables and Data Quality
- Monitoring and Support
- Common Mistakes
- Conclusion
For a company in Turkey that keeps its personnel data in SAP HCM, SGK (the Social Security Institution) is one of the external systems it talks to most often: with every hire, every termination, every medical report and every month end. Having HR enter these notices one by one on SGK's screens takes time and lets the record in SAP drift away from the record at SGK.
SGK offers official SOAP web services for hiring notices, termination notices and vizite (the employer's confirmation for sick-leave reports). In this article I describe a setup that connects to these services from SAP directly in ABAP, without middleware: the notice is born in the personnel action, travels to SGK through a queue and its result is stored in SAP. Method and field names are taken from the user guides SGK publishes; the guides are updated over time, so check the current version before you build.
Which Notice, Which Channel?
The four processes look as if they belong to the same institution, but their channels, authentication methods and triggers differ. Seeing these differences before design starts prevents many surprises later:
| Process | Channel | Authentication | Trigger in SAP |
|---|---|---|---|
| Hiring notice (işe giriş bildirgesi) | SGK 4A web service (SOAP) | User name, passwords and workplace registry number in every call | Hiring personnel action |
| Termination notice (işten ayrılış bildirgesi) | SGK 4A web service (SOAP), separate service | Same structure | Termination personnel action |
| Vizite (notice that the employee did not work) | SGK WS_Vizite web service (SOAP) | Separate user and password; a 30-minute token from the login method | Report query, HR confirmation |
| MUHSGK (combined withholding tax and premium/service return) | Revenue Administration (GİB) e-Beyanname, XML package | e-Beyanname credentials | Monthly payroll |
Architecture: Direct SOAP from ABAP
SGK's services speak SOAP over HTTPS addresses open to the internet. On the SAP side, a consumer proxy is generated from the WSDL for each service (with the Enterprise Service wizard in SE80); the service address and connection settings come from the logical port in SOAMANAGER, not from code. SGK publishes two separate services for hiring and termination; vizite has a single service.
| Service | Production | Test |
|---|---|---|
| Hiring | https://uyg.sgk.gov.tr/WS_SgkTescil4a/WS_SgkIseGirisService?wsdl | https://sgkt.sgk.gov.tr/WS_SgkTescil4a/WS_SgkIseGirisService?wsdl |
| Termination | https://uyg.sgk.gov.tr/WS_SgkTescil4a/WS_SgkIstenCikisService?wsdl | https://sgkt.sgk.gov.tr/WS_SgkTescil4a/WS_SgkIstenCikisService?wsdl |
| Vizite | https://uyg.sgk.gov.tr/Ws_Vizite/services/ViziteGonder | Confirm in the current guide |
Three technical details often get in the way during setup:
- HTTPS is mandatory. SGK's guide states explicitly that calls over HTTP will fail. For SAP to establish the connection, the certificate chain of SGK's server must be added to the SSL client PSE in STRUST.
- The WSDL imports its schema from outside. The 4A services' WSDL pulls its data types from a separate XSD address. SGK's guide recommends, for some development tools, downloading that XSD and fixing the
schemaLocationin the WSDL. If proxy generation from the URL fails, follow the same route and use the WSDL and XSD from local files. - Your own workplace isn't in the test environment by default. To work with your own workplace registry number in the test environment, you email SGK and ask for your workplace data to be loaded there; the address is in the guide.
The core of the architecture decision is this: the SGK call is not made inside the screen transaction that creates the notice. Every notice is first written to a queue table in SAP, sending happens in a separate step and the result is stored in the same row. The structure below is enough to convey the idea:
ZHR_SGK_BILDIRIM (SGK notification queue)
PERNR / BILDIRIM_TURU / OLAY_TARIHI employee, HIRE or EXIT, action date
TCKIMLIK_NO insured person's Turkish ID number
ISYERI_SICIL 26-digit workplace registry number
DURUM PENDING, SENT, TO_VERIFY,
DATA_ERROR, CONFIG_ERROR, RETRY
REFERANS_KODU code SGK returns for each successful notice
SGK_MESAJI / DENEME_SAYISI last response text, number of attempts
OLUSTURMA_ZAMANI / SON_DENEME_ZAMANIPutting the proxy behind a small client class, instead of exposing it to the rest of the application, gains two things: SGK's structures stay in one place, and the sending logic can be tested with ABAP Unit without connecting to SGK. I cover the general rules for destinations, logical ports and outbound service calls in the SAP integration scenarios article.
Credentials and Workplace Registry Numbers
The 4A services have no separate web service user; the workplace user from e-SGK is used, and the credentials are sent in the body of every call:
| Field | Content |
|---|---|
kullaniciAdi | 11-digit user name (the employer's Turkish ID number) |
isyeriKodu | Workplace code |
sistemSifre / isyeriSifre | System password and workplace password |
isyeriSicil | 26-digit workplace registry number; must match the user information |
The vizite service uses a different identity model: wsLogin is called with the vizite application's user code, workplace code and password, and the 36-character token it returns is used in subsequent calls and is valid for 30 minutes. You need to manage two separate sets of credentials for the two services.
In companies with several workplace registry numbers, each workplace has its own credentials. The mapping from the organisational assignment in SAP (personnel area and subarea) to the right registry number should be kept in a customising table, and every notice should go through it before it is sent.
Passwords travel in the body, hence three rules
Not in code or transports: passwords belong in configuration maintained per system and protected by an authorisation object, encrypted where possible; a password change should be a maintenance task, not a transport. Not in logs: mask the user information when you write requests to your own log. Careful with SRT_UTIL: the payload trace you switch on while hunting a SOAP error records the whole body, passwords included; keep the trace short and delete it afterwards.
The Hiring Notice
The trigger is the personnel action: when the hiring action is saved, a hiring row is written to the queue. I recommend sending it not inside the same save, but right after it in a separate step (bgRFC or a short-interval background job). That way the HR screen doesn't wait when SGK is slow, and the personnel action can be saved even while SGK is down.
Timing is a process decision. Under Law No. 5510, the hiring notice must as a general rule be filed at least one day before the employee starts work; for some workplaces such as construction, agriculture and fishing the deadline is the start day itself, and newly registered workplaces have a different period. This means HR has to enter the hiring action in advance, not on the employee's first day. A hiring entered retroactively is a late notice, however good the system is. Confirm current deadlines and exceptions against the legislation.
The main fields of a hiring record sent to SGK, and what to watch for:
| Field | Content | Watch for |
|---|---|---|
tckimlikNo, ad, soyad | Identity of the insured person | SGK uses the name in its own records; don't treat a different spelling in SAP as an error, report it |
giristarihi | Start date, dd.MM.yyyy | Start date of the personnel action |
sigortaliTuru | 0 all insurance branches, 7 apprentice, 8 social security support premium, 19 intern and others | Derive from employee group and subgroup through a mapping |
gorevkodu | 01 employer or representative, 02 worker, 05 apprentices and intern students and others | Mapping table |
meslekkodu | İŞKUR occupation code in 9999.99 or 9999.999 format | Not free text; from a valid code list with a format check |
csgbiskolu | Ministry of Labour industry code, 01–20 | Since 2019 the new industry codes must be sent |
kismiSureliCalisiyormu, kismiSureliCalismaGunSayisi | Y/N (E/H) and working days in the month (1–29) | From the work schedule for part-time employees |
ayniIsverenFarkliIsyeriNakil, nakilGeldigiIsyeriSicil | Transfer from another workplace of the same employer | For a transfer, both workplaces must share the tax or MERNIS number |
The iseGirisKaydet method accepts at most 10 insured persons per call. Pending queue entries are therefore split into packages of 10 per workplace registry number:
" pending: hiring entries waiting to be sent, all for the same workplace
" c_max_per_call = 10 (SGK: at most 10 insured persons per call)
DATA batch TYPE tt_queue.
LOOP AT pending INTO DATA(queued).
APPEND queued TO batch.
IF lines( batch ) = c_max_per_call.
send_batch( batch ).
CLEAR batch.
ENDIF.
ENDLOOP.
IF batch IS NOT INITIAL.
send_batch( batch ).
ENDIF.The response has two levels, and they must be handled separately. The package-level hataKodu says whether the request as a whole was accepted for processing: with a wrong workplace password or a mismatched registry number, no record in the package is processed. The record-level islemSonucu is the separate result for each insured person:
METHOD send_batch.
DATA request TYPE zsgk4a_ise_giris_parametre.
DATA response TYPE zsigortali_ise_giris_sonuclari.
request-kullanici_bilgileri = credentials->for_workplace( batch[ 1 ]-isyeri_sicil ).
request-sigortali_ise_giris_listesi = VALUE #( FOR entry IN batch ( to_sgk_entry( entry ) ) ).
request-ayni_isveren_farkli_isyeri_nakil = 'H'.
TRY.
proxy->ise_giris_kaydet( EXPORTING input = request
IMPORTING output = response ).
CATCH cx_ai_system_fault INTO DATA(fault).
" No response: SGK may have received the notice. Verify before sending again.
set_status( entries = batch status = c_status-verify text = fault->get_text( ) ).
RETURN.
ENDTRY.
" Level 1: the whole package
CASE response-hata_kodu.
WHEN 0.
" Package accepted for processing; results are per record
WHEN -101.
" System error on SGK's side: retry later
set_status( entries = batch status = c_status-retry text = response-hata_aciklamasi ).
RETURN.
WHEN OTHERS.
" Password, registry number, workplace status: nothing processed, a person must look
set_status( entries = batch status = c_status-config_error text = response-hata_aciklamasi ).
RETURN.
ENDCASE.
" Level 2: each insured person separately
LOOP AT response-sigortali_ise_giris_sonuc INTO DATA(result).
ASSIGN batch[ tckimlik_no = result-tckimlik_no ] TO FIELD-SYMBOL(<queued>).
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
CASE result-islem_sonucu.
WHEN 0.
" Without the stored reference code, no PDF of the notice can be retrieved
set_sent( entry = <queued> reference = result-referans_kodu ).
WHEN -101.
set_status( entries = VALUE #( ( <queued> ) ) status = c_status-retry
text = result-islem_aciklamasi ).
WHEN OTHERS.
" Data error: resending without a correction is pointless
set_status( entries = VALUE #( ( <queued> ) ) status = c_status-data_error
text = result-islem_aciklamasi ).
ENDCASE.
ENDLOOP.
ENDMETHOD.For each successful notice SGK generates a reference code, and the guide asks the employer to store it. The iseGirisPdfDokum method, which returns the PDF of the notice, works with this code; if the code isn't written to the queue, you have to go to SGK's screen for the printout.
The date format is a small but stubborn source of errors. SGK requests expect dd.MM.yyyy; formatting that depends on the user's date setting breaks silently when a background job runs under a different user:
METHOD to_sgk_date.
" Don't use DATE = USER or WRITE: the result would depend on the running user's settings
result = |{ date+6(2) }.{ date+4(2) }.{ date(4) }|.
ENDMETHOD.Even the guide itself shows dd/MM/yyyy for dates in query responses. Don't parse incoming dates assuming a single format; write a converter that accepts both.
The Termination Notice
The termination notice is filed within 10 days following the day the employment contract ends, using the istenCikisKaydet method. Batching and the two-level result check are the same as for hiring; the difference is in the data it carries.
- Termination reason:
istenCikisNedeniis a two-digit code ranging from termination during the probation period to retirement, transfer and collective dismissal. It should be derived from the reason of the termination action in SAP (MASSN/MASSG) through a mapping table. A wrong termination code directly affects the employee's entitlements such as unemployment benefit; this mapping should be approved row by row together with HR. - Period data: The notice carries
belgeturu(document type),hakedilenucret(earned wage),primikramiye(bonus),eksikgunsayisi(missing days) andeksikgunnedeni(missing-day reason) for the current and previous period. This data comes from payroll, but the deadline of the termination notice may pass before that month's payroll is final. Decide in advance which source the wage data comes from and how final it is. - Missing days: If a number of missing days is sent, the reason is mandatory and the number must be between 1 and 31. The reason code comes from the absence type in SAP through a mapping.
- Transfer: For a transfer to another workplace of the same employer before the contract ends, the termination reason is 16, and the 26-digit registry number of the target workplace is sent as well.
The service also offers a helper method called istenCikisDonemVeGunSayisiBul: based on the workplace and insured person sent, it returns the period start and end dates and day counts as SGK sees them. Comparing SAP's calculated day counts with this before sending moves one of the most contentious errors to before submission.
Timeouts and Duplicate Notices
Timeouts and temporary outages of SGK's services are among the issues that cause the most work in the field, and the design should expect them from the start. The real danger is assuming that a request that timed out never reached the other side. The request may have reached SGK and been processed, with only the response failing to come back. The request structure in the guide has no idempotency key; resending blindly risks a duplicate notice.
The safe route is to put the entry without a response into a to verify status and ask SGK before sending again. The 4A services offer query methods by Turkish ID number and date for this:
METHOD verify_before_resend.
" After a timeout: the request may have reached SGK and been processed.
" Ask first instead of resending blindly; if the record exists, don't send a second notice.
TRY.
DATA(answer) = sgk_client->find_entry( workplace = entry-isyeri_sicil
tckn = entry-tckimlik_no
date = to_sgk_date( entry-olay_tarihi ) ).
CATCH zcx_sgk_unavailable.
RETURN. " SGK still not responding; status unchanged, asked again in the next round
ENDTRY.
CASE answer-hata_kodu.
WHEN 0.
" Record found: the notice was already received, don't send again
set_status( entries = VALUE #( ( entry ) ) status = c_status-sent_no_reference
text = `Record found at SGK` ).
WHEN 1.
" No record: safe to send again
set_status( entries = VALUE #( ( entry ) ) status = c_status-pending text = `` ).
WHEN OTHERS.
set_status( entries = VALUE #( ( entry ) ) status = c_status-retry
text = answer-hata_aciklama ).
ENDCASE.
ENDMETHOD.Two details matter. The query response in the guide doesn't include the reference code; for a notice that timed out and was confirmed by query, the reference code doesn't come back this way, which is why it gets a status of its own. And the query methods are limited to at most 20 queries per 5 seconds: a job that verifies in bulk should put a short pause between queries.
Vizite: Fetching and Confirming Sick-Leave Reports
For SGK to be able to pay temporary incapacity benefit to an employee on medical leave, the employer has to file a "notice that the employee did not work" for that period. The WS_Vizite service lets you file this notice without using the web screen, and the rules of the web application apply to the service as well.
In our setup the flow has two parts: reports are fetched from SGK and stored in SAP; HR gives the confirmation from a list in SAP. Fetching reports has a rule of its own. RaporAramaTarihile returns the first 100 open reports whose clinic date is earlier than the given date. If the reports read aren't closed with RaporOkunduKapat, later queries return the same reports and, in the guide's words, access to other reports is blocked:
METHOD fetch_open_reports.
DATA(token) = vizite->login( ). " wsLogin: token valid for 30 minutes
DO.
" First 100 open reports with a clinic date earlier than the given date
DATA(reports) = vizite->find_reports_before( token = token
date = sy-datum + 1 ).
IF reports IS INITIAL.
EXIT.
ENDIF.
" Table read by the HR confirmation list; written by report ID, so a repeat overwrites
save_for_approval( reports ).
COMMIT WORK AND WAIT.
" Close as read only AFTER the record is persisted
LOOP AT reports INTO DATA(report).
vizite->mark_as_read( token = token report_id = report-medula_rapor_id ).
ENDLOOP.
IF lines( reports ) < 100.
EXIT.
ENDIF.
ENDDO.
ENDMETHOD.The order is deliberate. If the program stops after a report is closed but before it is saved in SAP, the report won't come back in the next date query and is missed. If it stops after saving but before closing, the report comes back in the next round; because records are written by report ID, no harm is done. In a long-running job, account for the token's 30-minute lifetime too: when a token error comes back, log in again.
The HR confirmation list shows, for each report, the outpatient and inpatient treatment dates, the return-to-work check date, the case type and the report status. When HR confirms a report, RaporOnay is called with this information:
| Field | Values |
|---|---|
| Vaka (case) | 1 occupational accident, 2 occupational disease, 3 illness, 4 maternity |
| MedulaRaporId | Report ID from the report query |
| NitelikDurumu | 0 did not work, 1 worked |
The service also offers OnayIptal to reverse a confirmation given by mistake, PersonelimDegildir ("not my employee") to transfer a report of an insured person who doesn't belong to your workplace to the right one, and OnayliRaporlarTarihile to list confirmed reports by date range. Both correction paths should be available in the confirmation list so HR doesn't have to go back to SGK's screen.
MUHSGK: The Return Goes to the Revenue Administration
Monthly premium and service data is filed with the Revenue Administration (GİB), combined with the withholding tax return. In our setup, SAP payroll produced the return data as XML conforming to the schema GİB publishes, and a separate program uploaded this file to GİB's system automatically. SAP's responsibility isn't the submission but the correctness of the data submitted:
- The schema changes over time. GİB may update the return schema and its checks. Validating the XML against the current schema before handing it over moves errors from the upload stage to the generation stage.
- Consistency of missing days. The number and reason of missing days in the return must not contradict what was reported in the termination notice or in the vizite confirmations.
- Reconciliation. Totals in the generated file (number of insured persons, earnings subject to premium, days) should be compared with payroll results, and the generated file should be archived with its period and creation time. That archive is the answer to "what data did we send?".
Code Tables and Data Quality
A significant share of the rejection messages that cause work in the field are about data, not technology. Most codes in the notices are lists with no one-to-one equivalent in SAP, but which can be derived from SAP data. Instead of writing this derivation into the program, keep it in customising tables with validity dates, because code lists change; the 2019 industry code change is one example.
| Code list | Where | Source in SAP |
|---|---|---|
| Insured type, duty code | Hiring | Mapping from employee group / subgroup |
| Occupation code (İŞKUR) | Hiring, termination | Tying it to the job or position is more reliable than entering it per employee |
| Ministry of Labour industry code | Hiring, termination | Customising per workplace |
| Termination reason | Termination | Mapping from the termination action reason (MASSN/MASSG) |
| Document type | Termination, MUHSGK | From insured type and payroll rules |
| Missing-day reason | Termination, MUHSGK | Mapping from absence type |
| Case type, report status | Vizite | Comes from SGK; shown with readable text in the HR list |
The most effective check is the one done before sending. Rules such as the occupation code format, the 1–29 day range for part-time work, a filled reason when missing days are entered, and a 26-digit target registry number for transfers can be raised in SAP as warnings when the personnel action is saved. Showing the error to HR at the moment they enter the action is much cheaper than interpreting SGK's rejection message the next day.
Monitoring and Support
The daily user of this setup is HR, not the developer. The monitoring screen should therefore be designed for HR:
- Each notice's status, SGK's last message and the number of attempts should be visible in one list; an entry with a data error should be resendable from the same screen once corrected.
- Entries in configuration error status (password, registry number) should reach the person responsible by email or workflow; while they wait, every notice for that workplace is blocked.
- The PDF of a successful notice should be retrievable on demand with the stored reference code.
- Technical details (SOAP error, timeout) should be written to the application log (SLG1), searchable by personnel number.
Common Mistakes
Embedding the SGK call in the personnel action
When SGK slows down the HR screen freezes; when SGK is down the personnel action can't be saved. Write the notice to a queue and leave sending to a separate step.
Resending blindly after a timeout
A request without a response may have been processed. Query first; send only if there is no record.
Not storing the reference code
SGK's guide asks for it explicitly, and the PDF printout is retrieved with it. If the queue table has no reference code field, the design is incomplete.
Hard-coding the codes
Industry, occupation, termination reason and missing-day codes change. A CASE in the program means a transport and a test cycle for every change; a table with validity dates means a maintenance task.
Connecting to production SGK from a test system
A hiring notice from a test system to the production service creates a real insurance record. The logical ports of test systems should point to SGK's test address, and after a system copy these ports should be among the first items on the checklist.
Closing a vizite report before saving it
A report closed as read but not written to SAP won't come back in later date queries. Save and commit first, then close.
Conclusion
The technical part of an SGK integration is surprisingly small: a few consumer proxies, a queue table and a monitoring screen. The real work is producing the right data at the right time and telling error types apart: a password error should go to an administrator, a data error to HR, and a timeout to a query first. Once that distinction is made, HR looks at a single list in SAP instead of SGK's screens.
You can find the scope I offer for public-sector and external system integrations like this on the integration services page.
Get support on SAP HCM to SGK notice integration, reviewing an existing setup or improving flows that keep failing.
Request an Initial Consultation