הסרה מרשימת תפוצה ורגולציה
החוק בישראל מחייב הודעות פרסומיות לשאת דרך יציאה שעובדת, ומחייב אתכם לכבד אותה. ה-API נותן לכם את המנגנון; העמוד הזה מסביר איך לחבר אותו כך ש"הסרה" תהיה באמת הסרה בכל המערכת שלכם.
זה לא ייעוץ משפטי
העמוד מתאר את התנהגות ה-API. חוק התקשורת (חוק הספאם) קובע מה עליכם לעשות בפועל, כולל תיעוד וחובת ההוכחה לגבי הסכמה. התייעצו עם מי שמייעץ לכם בנושאי רגולציה.
צעד 1 — לשים יציאה בהודעה
add_unsubscribe ב-sms וב-bulk מוסיף את מנגנון ההסרה:
| ערך | תוצאה |
|---|---|
3 | מוסיף קישור הסרה. |
2 | מוסיף הוראות הסרה בהודעה חוזרת. |
| כל ערך אחר | לא מתווסף דבר. |
<?xml version="1.0" encoding="UTF-8"?>
<sms>
<user>
<username>Leeroy</username>
</user>
<source>DemoAPI</source>
<destinations>
<cl_id>21518</cl_id>
</destinations>
<message>New collection now in store.</message>
<add_unsubscribe>3</add_unsubscribe>
<campaign_name>august-newsletter</campaign_name>
</sms>{
"sms": {
"user": { "username": "Leeroy" },
"source": "DemoAPI",
"destinations": { "cl_id": "21518" },
"message": "New collection now in store.",
"add_unsubscribe": "3",
"campaign_name": "august-newsletter"
}
}ערך לא תקין מכשיל את השליחה בסטטוס 986 ולא שולח אותה בלי יציאה — וזה הכיוון הנכון.
איזה ערך?
3 (קישור) הוא הקלקה אחת ואינו דורש פרשנות, ולכן הוא מייצר נתוני הסרה נקיים יותר. 2 (תשובה) עובד במכשירים שבהם לחיצה על קישור מסורבלת, אבל מגיע כטקסט חופשי שצריך להמשיך להתאים — ראו טיפול בתשובות. אם חייבים לבחור אחד, בחרו 3.
הטקסט שמתווסף עולה תווים במגבלת 1005 התווים. השאירו מרווח.
צעד 2 — לתת ל-TextMe לדכא את השליחות שלה
כשמישהו מבקש להסירו, TextMe מוסיפה את המספר לרשימת החסימה של החשבון ומפסיקה למסור אליו. אינכם צריכים לסנן יעדים בעצמכם:
- מספרים חסומים מושמטים בזמן השליחה.
- שליחה שכל יעדיה חסומים נכשלת בסטטוס
8ולא מדווחת הצלחה מדומה. - דוחות מסירה עבור יעדים חסומים חוזרים כסטטוס
201, נחסם לפי בקשה.
זה מכסה SMS. זה לא מכסה שום דבר אחר שאתם שולחים.
צעד 3 — להפיץ את ההסרה למערכות שלכם
זה הצעד שרוב האינטגרציות מפספסות
TextMe עוצרת את ההודעות שלה. הדואר שלכם, ההתראות שלכם, מרכז השירות שלכם וכל ספק SMS אחר שאתם משתמשים בו לא יודעים על זה כלום. מי שביקש שיניחו לו וממשיך לשמוע מכם בשלושה ערוצים אחרים — יש לו תלונה מוצדקת, ועל הנייר כיבדתם את הבקשה.
צרכו את ערוץ ה-Push של רשימת החסימה, שנשלח בכל פעם שמספר נחסם בכל דרך שזה קרה — קישור, תשובה, או פעולה יזומה:
POST https://your-app.example.com/textme/blacklist
Content-Type: application/x-www-form-urlencoded
message=נחסם+מנוי&date=01/04/26 16:05:05&dest=9725xxxxxxxxapp.post('/textme/blacklist', (req, res) => {
const phone = toLocal(req.body.dest) // 9725xxxxxxxx → 05xxxxxxxx
const at = req.body.date
// אשרו לפני העבודה — תשובה איטית מביאה להשבתת הערוץ.
res.sendStatus(200)
queue.push({ kind: 'opt-out', phone, at })
})
// ב-worker: דיכוי בכל מקום, לא רק ב-SMS.
async function handleOptOut({ phone, at }) {
await contacts.suppress(phone, {
channels: ['sms', 'email', 'push', 'voice'],
reason: 'sms-opt-out',
recordedAt: at,
})
}@app.post("/textme/blacklist")
def blacklisted():
queue.put({
"kind": "opt-out",
"phone": to_local(request.form.get("dest")), # 9725… → 05…
"at": request.form.get("date"),
})
# אשרו לפני העבודה.
return "", 200
# ב-worker: דיכוי בכל מקום, לא רק ב-SMS.
def handle_opt_out(phone, at):
contacts.suppress(
phone,
channels=["sms", "email", "push", "voice"],
reason="sms-opt-out",
recorded_at=at,
)<?php
Route::post('/textme/blacklist', function (Illuminate\Http\Request $request) {
// 9725xxxxxxxx → 05xxxxxxxx לפני שזה נוגע בטבלת אנשי הקשר.
SuppressContact::dispatch(
phone: to_local($request->input('dest')),
recordedAt: $request->input('date'),
);
return response()->noContent(200);
});זכרו ש-dest מגיע בצורה בינלאומית (9725xxxxxxxx) בעוד הרשומות שלכם כמעט בוודאות מחזיקות את הצורה המקומית — נרמלו לפני התאמה, כמו בהודעות נכנסות ו-Push.
צעד 4 — לשמור על מסלול ביקורת
שני דברים כדאי לתעד בכל הסרה, כי הם מה שתצטרכו להציג אם תיתקלו בערעור:
- מתי זה קרה. גם ערוץ ה-Push וגם דוח ה-
blacklistנושאים את התאריך. - מתי ואיך ניתנה ההסכמה מלכתחילה. ה-API לא יכול לספר לכם את זה — זה נמצא ברשומות ההרשמה שלכם.
דוח רשימת החסימה נותן לכם התאמה תקופתית:
<?xml version="1.0" encoding="UTF-8"?>
<blacklist>
<user>
<username>XXXXXX</username>
</user>
<from>02/06/22 00:00</from>
<to>15/12/22 23:59</to>
</blacklist>{
"blacklist": {
"user": {
"username": "XXXXXX"
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"blacklist": {
"user": {
"username": "XXXXXX"
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59"
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
blacklist: {
user: {
username: 'XXXXXX',
},
from: '02/06/22 00:00',
to: '15/12/22 23:59',
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client([
'headers' => [
'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
'Accept' => 'application/json',
],
]);
$response = $client->post('https://my.textme.co.il/api', [
'json' => [
'blacklist' => [
'user' => [
'username' => 'XXXXXX',
],
'from' => '02/06/22 00:00',
'to' => '15/12/22 23:59',
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'blacklist' => [
'user' => [
'username' => 'XXXXXX',
],
'from' => '02/06/22 00:00',
'to' => '15/12/22 23:59',
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# pip install httpx
import os
import httpx
response = httpx.post(
"https://my.textme.co.il/api",
headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
json={
"blacklist": {
"user": {
"username": "XXXXXX",
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59",
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"blacklist": map[string]any{
"user": map[string]any{
"username": "XXXXXX",
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59",
},
})
req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result struct {
Status json.Number `json:"status"`
Message string `json:"message"`
}
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
panic(err)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeBlacklistGet {
public static void main(String[] args) throws Exception {
String body = """
{
"blacklist": {
"user": {
"username": "XXXXXX"
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59"
}
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
.header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"blacklist": {
"user": {
"username": "XXXXXX"
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59"
}
}
""";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));
var response = await http.PostAsync("https://my.textme.co.il/api",
new StringContent(payload, Encoding.UTF8, "application/json"));
var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);require "net/http"
require "json"
uri = URI("https://my.textme.co.il/api")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
"blacklist" => {
"user" => {
"username" => "XXXXXX",
},
"from" => "02/06/22 00:00",
"to" => "15/12/22 23:59",
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let result: Value = reqwest::Client::new()
.post("https://my.textme.co.il/api")
.bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
.json(&json!({
"blacklist": {
"user": {
"username": "XXXXXX"
},
"from": "02/06/22 00:00",
"to": "15/12/22 23:59"
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}<?xml version="1.0" encoding="UTF-8"?>
<blacklist>
<status>0</status>
<message></message>
<transactions>
<transaction>
<phone>5********</phone>
<date>03/12/14 16:38</date>
</transaction>
</transactions>
</blacklist>{
"status": 0,
"message": "",
"transactions": [
{
"phone": "5********",
"date": "22/08/22 11:00"
},
{
"phone": "5********",
"date": "23/11/22 12:37"
}
]
}המספרים חוזרים ממוסכים
הרשומות מוחזרות כ-5******** ולא במלואן. הדוח הוא ביקורת של כמה ביקשו להסירם ומתי — הוא לא רשימה שאפשר להשוות מול מסד הנתונים שלכם. השתמשו בערוץ ה-Push לצורך הזה, וסמכו על ה-API שיאכוף את רשימת החסימה בזמן השליחה.
צעד 5 — לחסום לפי בקשה, בלי לחכות לתשובה
לקוחות מבקשים להוסר בטלפון, במייל, בדלפק. הוסיפו אותם בעצמכם:
<?xml version="1.0" encoding="UTF-8"?>
<addNumBL>
<user>
<username>xxxxxx</username>
</user>
<phones>
<phone>5********</phone>
<phone>05********</phone>
</phones>
</addNumBL>{
"addNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
}
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"addNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
}
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
addNumBL: {
user: {
username: 'xxxxxx',
},
phones: {
phone: [
'5********',
'05********',
],
},
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client([
'headers' => [
'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
'Accept' => 'application/json',
],
]);
$response = $client->post('https://my.textme.co.il/api', [
'json' => [
'addNumBL' => [
'user' => [
'username' => 'xxxxxx',
],
'phones' => [
'phone' => [
'5********',
'05********',
],
],
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'addNumBL' => [
'user' => [
'username' => 'xxxxxx',
],
'phones' => [
'phone' => [
'5********',
'05********',
],
],
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# pip install httpx
import os
import httpx
response = httpx.post(
"https://my.textme.co.il/api",
headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
json={
"addNumBL": {
"user": {
"username": "xxxxxx",
},
"phones": {
"phone": [
"5********",
"05********",
],
},
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"addNumBL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
"phones": map[string]any{
"phone": []any{
"5********",
"05********",
},
},
},
})
req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result struct {
Status json.Number `json:"status"`
Message string `json:"message"`
}
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
panic(err)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeBlacklistAdd {
public static void main(String[] args) throws Exception {
String body = """
{
"addNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
.header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"addNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
}
}
}
""";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));
var response = await http.PostAsync("https://my.textme.co.il/api",
new StringContent(payload, Encoding.UTF8, "application/json"));
var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);require "net/http"
require "json"
uri = URI("https://my.textme.co.il/api")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
"addNumBL" => {
"user" => {
"username" => "xxxxxx",
},
"phones" => {
"phone" => [
"5********",
"05********",
],
},
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let result: Value = reqwest::Client::new()
.post("https://my.textme.co.il/api")
.bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
.json(&json!({
"addNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
}
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}ההצלחה היא 946, לא 0
addNumBL עונה 946 — number has successfully added to blacklist. קוד שמתייחס לכל סטטוס שאינו אפס ככשל ידווח על קריאה תקינה כשבורה, ואולי ינסה אותה שוב לחינם.
הסרת חסימה
הסרת מישהו מרשימת החסימה פירושה לשלוח הודעה לאדם שביקש בעבר שתפסיקו. לכן rmNumBL דורשת reason:
<?xml version="1.0" encoding="UTF-8"?>
<rmNumBL>
<user>
<username>xxxxxx</username>
</user>
<phones>
<phone>5********</phone>
<phone>05********</phone>
</phones>
<reason>Customer opted back in by phone</reason>
</rmNumBL>{
"rmNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
},
"reason": "Customer opted back in by phone"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"rmNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
},
"reason": "Customer opted back in by phone"
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
const response = await fetch('https://my.textme.co.il/api', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
rmNumBL: {
user: {
username: 'xxxxxx',
},
phones: {
phone: [
'5********',
'05********',
],
},
reason: 'Customer opted back in by phone',
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client([
'headers' => [
'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
'Accept' => 'application/json',
],
]);
$response = $client->post('https://my.textme.co.il/api', [
'json' => [
'rmNumBL' => [
'user' => [
'username' => 'xxxxxx',
],
'phones' => [
'phone' => [
'5********',
'05********',
],
],
'reason' => 'Customer opted back in by phone',
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'rmNumBL' => [
'user' => [
'username' => 'xxxxxx',
],
'phones' => [
'phone' => [
'5********',
'05********',
],
],
'reason' => 'Customer opted back in by phone',
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# pip install httpx
import os
import httpx
response = httpx.post(
"https://my.textme.co.il/api",
headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
json={
"rmNumBL": {
"user": {
"username": "xxxxxx",
},
"phones": {
"phone": [
"5********",
"05********",
],
},
"reason": "Customer opted back in by phone",
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"rmNumBL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
"phones": map[string]any{
"phone": []any{
"5********",
"05********",
},
},
"reason": "Customer opted back in by phone",
},
})
req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result struct {
Status json.Number `json:"status"`
Message string `json:"message"`
}
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
panic(err)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeBlacklistRemove {
public static void main(String[] args) throws Exception {
String body = """
{
"rmNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
},
"reason": "Customer opted back in by phone"
}
}
""";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
.header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"rmNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
},
"reason": "Customer opted back in by phone"
}
}
""";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));
var response = await http.PostAsync("https://my.textme.co.il/api",
new StringContent(payload, Encoding.UTF8, "application/json"));
var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);require "net/http"
require "json"
uri = URI("https://my.textme.co.il/api")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
"rmNumBL" => {
"user" => {
"username" => "xxxxxx",
},
"phones" => {
"phone" => [
"5********",
"05********",
],
},
"reason" => "Customer opted back in by phone",
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let result: Value = reqwest::Client::new()
.post("https://my.textme.co.il/api")
.bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
.json(&json!({
"rmNumBL": {
"user": {
"username": "xxxxxx"
},
"phones": {
"phone": [
"5********",
"05********"
]
},
"reason": "Customer opted back in by phone"
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}גם סטטוסי ההצלחה שלה יוצאי דופן: 0 כשכל המספרים הוסרו, ו-944 כשחלקם לא היו ברשימה מלכתחילה.
להסיר חסימה רק עם הוכחה
עשו זאת כשיש לכם תיעוד של האדם שמבקש לחזור — הרשמה חדשה, בקשה בכתב. reason הוא המקום שבו התיעוד הזה נרשם. חובת ההוכחה לגבי הסכמה מוטלת על השולח, ולכן "הם בטח לא מתנגדים" אינו בסיס להרשמה מחדש של אף אחד.
מנגנוני דיכוי קרובים
לא כל דבר שעוצר הודעה הוא הסרה. כדאי להבחין ביניהם בדיווח שלכם:
| מנגנון | מה זה |
|---|---|
temp_bl | הבחירה שלכם לדלג על מי שקיבל הודעה ב-1–14 הימים האחרונים. הגבלת תכיפות, לא הסכמה. סינון של כולם מחזיר סטטוס 715. |
סטטוס מסירה 17 | המנוי חסום להודעות פרסומיות ברמת המפעיל או הרגולטור. |
סטטוס מסירה 201 | נחסם לפי בקשת המנוי עצמו — ההסרה כפי שהיא מופיעה בנתוני המסירה. |
סטטוס מסירה 15 | מכשיר כשר (מסונן). זו אינה סירוב — הגיעו אליו בקול במקום. |
סטטוס 8 | כל היעדים בשליחה היו ברשימת החסימה. בעיית קהל, לא תקלת קוד. |
15 מצדיק הדגשה: התייחסות אליו כאל הסרה זורקת לקוח שניתן להשגה. הוא אומר המכשיר הזה לא עושה SMS, ו-tts.type בערך 2 מנתב בדיוק את המספרים האלה לשיחה קולית אוטומטית.
צ'ק-ליסט עובד
- כל שליחה שיווקית נושאת
add_unsubscribe—3אלא אם יש סיבה אחרת. - ערוץ ה-Push של רשימת החסימה נצרך, ומדכא בכל הערוצים.
946ו-944מטופלים כהצלחה בקוד שלכם.- מילות מפתח נכנסות
STOP/הסרמטופלות כמסלול שני, למקרה של2. - סטטוסי מסירה
201ו-17נכתבים בחזרה לרשומת איש הקשר. - סטטוס מסירה
15מנותב לקול ולא מסומן ככשל. - סטטוס
8מעלה התראת קהל, לא התראת קוד. - הסרת חסימה דורשת סיבה מתועדת.
הצעד הבא
- הודעות נכנסות ו-Push — מסלול התשובות עבור
add_unsubscribeבערך2 - רשימת חסומים — תיעוד הפעולות המלא
- סטטוסי מסירה — כל קודי החסימה והסירוב

