Available — Local Digital Products
HCA · Studio
TR — Contact ↗
Technical Note 10 · Integration

The NetGSM REST API and its silent failures

The most common failure in SMS integrations is not a failed request — it is a request that looks successful while no message is sent. You get HTTP 200, your code is happy, the phone is quiet. Nobody notices until someone reads the code in the response body.

Service
NetGSM REST v2
Language
PHP
Symptom
200 but no SMS
Short answer

The NetGSM REST endpoint returns HTTP 200 to say it received your request; whether the message was actually accepted is a code inside the response body. 00 is success, 30 means bad credentials or an IP restriction, 40 an unapproved sender name, 20 a length or character problem. An integration that checks only the HTTP status makes every one of these invisible. On top of that the number must be in 5XXXXXXXXX form (no leading zero, no country code), msgheader must be an approved sender, and commercial messages are subject to Turkey's consent registry.

The request

The REST endpoint takes a JSON body with basic authentication. The smallest working example:

<?php
$payload = [
  'msgheader' => 'APPROVED_SENDER',
  'encoding'  => 'TR',
  'messages'  => [
    ['msg' => 'Your registration has been received.', 'no' => '5301234567'],
  ],
];

$ch = curl_init('https://api.netgsm.com.tr/sms/rest/v2/send');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST           => true,
  CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
  CURLOPT_USERPWD        => NETGSM_USER . ':' . NETGSM_PASS,
  CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
  CURLOPT_TIMEOUT        => 15,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

The JSON_UNESCAPED_UNICODE flag matters: sending Turkish characters as \u011f-style escapes causes trouble in some setups.

Response codes

The critical point: $http is almost always 200. The real outcome is in the body.

00Success — the message was queued
20Message text invalid or the character limit was exceeded
30Wrong credentials or API access restricted by IP
40The sender header is not approved
50Blocked by the consent registry — recipient refuses commercial messages
70Invalid parameter; the body shape differs from what is expected

Code 30 wastes the most time, because it appears even when the credentials are correct. A NetGSM account can restrict API access to specific IP addresses; code that works from your development machine stops the moment it is deployed. Testing before adding the server's outbound IP in the panel is wasted effort.

Number format

The service expects 5XXXXXXXXX: ten digits, no leading zero, no country code. Real numbers in your database are never that tidy — users type spaces, parentheses, dashes, +90 and leading zeros.

A single normalisation step before sending removes most failures:

function netgsm_number(string $raw): ?string {
    $d = preg_replace('/\D+/', '', $raw);        // drop non-digits
    if (str_starts_with($d, '90')) $d = substr($d, 2);
    if (str_starts_with($d, '0'))  $d = substr($d, 1);
    return preg_match('/^5\d{9}$/', $d) ? $d : null;
}

Returning null and recording it, rather than sending an invalid number, avoids both wasted credits and failures you cannot diagnose later.

Turkish characters

There is a trade-off here. Setting encoding to TR renders Turkish letters correctly, but the message is encoded as Unicode and a single SMS drops from 160 characters to 70. Longer text is split across multiple credits.

The alternative is to fold the text down to ASCII: ş→s, ğ→g, ı→i, ö→o, ü→u, ç→c. You keep 160 characters at the cost of how the text reads. The right choice depends on content: ASCII is fine for verification codes and appointment reminders, while corporate announcements usually deserve correct spelling.

Whichever you pick, count characters before sending and record how many credits the message will consume. Otherwise the monthly invoice becomes a surprise.

In Turkey, sending commercial electronic messages requires consent registered in a national system. Informational messages — order status, appointment reminders, verification codes — fall outside that scope; campaign and promotional content falls inside it.

Make that distinction in code as well: apply the consent check to commercial sends and skip it for transactional ones. Routing both through a single function leads either to transactional messages being blocked needlessly, or to commercial messages going out where they should not.

A robust integration

The minimum an SMS layer should satisfy before it goes to production:

  • Read the code in the body; never trust the HTTP status
  • Record every send: number, text, returned code, timestamp
  • Set a timeout (CURLOPT_TIMEOUT) so a slow SMS provider cannot stall your checkout
  • Decouple sending from the user's request — queue it, send in the background
  • Normalise and validate the number before sending
  • Keep credentials out of the code; use environment variables

Recording matters most. When a "the SMS never arrived" complaint comes in, being able to see the returned code separates the cases in seconds: 00 means it was handed to the operator and the problem is downstream; 40 means the sender approval lapsed; no record at all means the send was never attempted.

Summary

SMS integrations look simple, which is exactly why they get written carelessly. The habit that matters fits in one sentence: the carrier's HTTP response does not tell you the business outcome. Read the code in the body, log every send, normalise numbers, and keep sending off the main request path. Those four points eliminate almost all SMS-related debugging.

Quick reference
Endpoint
POST https://api.netgsm.com.tr/sms/rest/v2/send, basic auth
Critical
HTTP 200 always arrives — the outcome is the code in the body, 00 is success
Code 30
Bad credentials or API access restricted by IP
Code 40
Sender header is not approved
Number
5XXXXXXXXX — no leading zero, no country code
Turkish
encoding: TR renders correctly but drops the limit from 160 to 70
Consent
Required for commercial messages; transactional messages are out of scope
Resilience
Set a timeout, queue the send, record every attempt
Frequently Asked Questions

About the NetGSM integration.

The request returns 200 but no SMS is sent. Why?

Because the HTTP status only tells you the request arrived; whether the send was accepted is a code in the response body. 00 is success, 30 is credentials or IP restriction, 40 is an unapproved sender header, 20 is a message or character problem. If your integration does not read the body, all of these stay invisible.

I get code 30 but my username and password are correct.

That code covers IP restriction as well as bad credentials. A NetGSM account can limit API access to specific IP addresses, which is why code that works on your development machine stops after deployment. Add the server's outbound IP to the allowed list in the panel.

Should I use Turkish characters?

It depends on the content. Setting encoding to TR renders letters correctly but switches the message to Unicode, dropping the single-SMS limit from 160 to 70 characters; longer text is split across several credits. For short transactional messages such as verification codes, folding to ASCII noticeably reduces cost.

Does every message need a consent check?

No. The obligation applies to commercial electronic messages. Transactional messages such as order status, appointment reminders and verification codes are out of scope. If you do not separate the two in code, you will either block transactional messages needlessly or send commercial ones where you should not.

Availability and Quotes

Have an idea?
Half a sentence is enough.