Push API
ההפך משאר התיעוד הזה: במקום שאתם תקראו ל-TextMe, TextMe קוראת לכתובת שבשליטתכם. חמישה ערוצים יכולים להישלח ב-Push, מה שמייתר את הצורך לתשאל את הדוחות כליל.
אתם רושמים כתובת בעצמכם, או עם הפעולה push_url או בממשק הניהול תחת הגדרות, Push API. אין צורך לפנות ל-TextMe.
רישום כתובת
כותרת העמוד היא URL assignment to. בחרו משתמש ברשימת ה-Users בצד, ואז השתמשו ב-Addition of URL. הכתובות הרשומות מופיעות בטבלה עם עמודת Service, כלומר כתובת משויכת לערוץ מסוים ולא קולטת את כל הערוצים יחד, ולמשתמש יכולה להיות יותר מכתובת אחת.
תפריט URL type מציע בדיוק חמישה ערכים:
| URL type | הערוץ |
|---|---|
dlr | דוחות מסירה של SMS |
incoming_sms | SMS נכנס |
blacklist | הוספות לרשימת החסימה |
dlr_whatsapp | דוחות מסירה של WhatsApp |
incoming_whatsapp | WhatsApp נכנס |
שני ערוצי ה-WhatsApp הם רישומים נפרדים. כתובת dlr אינה מקבלת דוחות מסירה של WhatsApp.
הכתובות הן לכל משתמש, לא לכל חשבון
פאנל המשתמשים הוא כל העניין במסך הזה. בחשבון עם כמה משתמשים, לכל אחד יש כתובות משלו, והרישום מתחיל בבחירת המשתמש. חשבון אב עם הרבה משתמשי משנה רושם כל אחד בנפרד; אין ייבוא מרוכז בעמוד הזה.
בטבלה יש גם עמודת Status ועמודת Actions, ושם מופיע ערוץ שכובה אחרי כשלי מסירה חוזרים, ושם מטפלים בו.
רישום עם push_url
עושה בדיוק מה שעושה מסך הקונסולה שלמעלה, בלי לפתוח אותו. קריאה אחת רושמת כתובת אחת עבור ערוץ אחד.
POST https://my.textme.co.il/apiפרמטרים
| שם | סוג | תיאור | חובה |
|---|---|---|---|
push_url | object | מכיל את כל האלמנטים האחרים. | ✔️ |
user | object | מכיל את אלמנט המשתמש. | ✔️ |
username | string | שם המשתמש של החשבון שבו אתם מזוהים במערכת. | ✔️ |
type | string | הערוץ שברצונכם לקבל בכתובת הזו. אחד מתוך dlr, incoming_sms, blacklist, dlr_whatsapp, incoming_whatsapp. | ✔️ |
url | string | הכתובת שאליה נשלח. חייבת להיות כתובת http או https ציבורית. | ✔️ |
דוגמת בקשה
<?xml version="1.0" encoding="UTF-8"?>
<push_url>
<user>
<username>Leeroy</username>
</user>
<type>dlr</type>
<url>https://www.example.com/path/to/resource</url>
</push_url>{
"push_url": {
"user": {
"username": "Leeroy"
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"push_url": {
"user": {
"username": "Leeroy"
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource"
}
}'// 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({
push_url: {
user: {
username: 'Leeroy',
},
type: 'dlr',
url: 'https://www.example.com/path/to/resource',
},
}),
})
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' => [
'push_url' => [
'user' => [
'username' => 'Leeroy',
],
'type' => 'dlr',
'url' => 'https://www.example.com/path/to/resource',
],
],
]);
$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', [
'push_url' => [
'user' => [
'username' => 'Leeroy',
],
'type' => 'dlr',
'url' => 'https://www.example.com/path/to/resource',
],
])
->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={
"push_url": {
"user": {
"username": "Leeroy",
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource",
},
},
)
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{
"push_url": map[string]any{
"user": map[string]any{
"username": "Leeroy",
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource",
},
})
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 TextMePushUrlAdd {
public static void main(String[] args) throws Exception {
String body = """
{
"push_url": {
"user": {
"username": "Leeroy"
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource"
}
}
""";
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+ using System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"push_url": {
"user": {
"username": "Leeroy"
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource"
}
}
""";
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({
"push_url" => {
"user" => {
"username" => "Leeroy",
},
"type" => "dlr",
"url" => "https://www.example.com/path/to/resource",
},
})
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!({
"push_url": {
"user": {
"username": "Leeroy"
},
"type": "dlr",
"url": "https://www.example.com/path/to/resource"
}
}))
.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"?>
<sms>
<status>0</status>
<message>push url has successfully added</message>
</sms>{
"status": 0,
"message": "push url has successfully added"
}הפעולה מוסיפה כתובת. היא לעולם אינה משנה כתובת קיימת.
אם לחשבון כבר יש כתובת עבור אותו type, הקריאה מחזירה סטטוס 517 ושום דבר לא משתנה. הכתובת הישנה נשארת בדיוק במקומה.
כדי להעביר ערוץ למקום אחר, מחקו קודם את הרשומה הקיימת בממשק הניהול, ורק אז קראו ל-push_url.
כדאי לשים לב לזה, כי הכשל שקט. סקריפט פריסה שרושם מחדש בכל פעם יקבל 517, ימשיך כאילו הצליח, ויותיר את הערוץ מכוון לכתובת הקודמת. הכתובת החדשה שלכם לא תקבל דבר, ותיראה תקולה כשהיא תקינה לגמרי.
כל ערוץ נרשם בנפרד. אם אתם רוצים את כל החמישה, אלה חמש קריאות.
איך הודעת Push מגיעה
POST https://your-app.example.com/textme/dlr
Content-Type: application/x-www-form-urlencodedכל ערוץ שולח את אותם שמות שדות כמו בתשובת ה-XML המתושאלת, בצורה שטוחה: שדות טופס, לא XML ולא JSON. קראו אותם בדיוק כפי שאתם קוראים שליחת טופס HTML.
שום דבר לא מאמת את הבקשה
הודעת Push אינה נושאת טוקן, חתימה או סוד משותף. כל מי שילמד את הכתובת שלכם יכול לשלוח אליה. הגנו עליה בעצמכם:
- השתמשו בנתיב שלא ניתן לנחש, והתייחסו אליו כאל פרט גישה.
- אם אפשר, הגדירו רשימת כתובות המקור של TextMe בשכבת הקצה.
- הפכו את הטיפול לאידמפוטנטי (לפי
external_idאו לפיphone+date) כי הודעה שנשלחה שוב אינה ניתנת להבחנה מהודעה חדשה. - אף פעם לא לפעול על סמך Push לבדו במשהו בלתי הפיך; אמתו קודם מול
dlr.
החזירו 200, ומהר
כל תשובה שאינה 200 OK נחשבת כשל. TextMe מחזיקה את ההודעה ומנסה שוב לזמן מה, ואז מפסיקה לשלוח לכתובת שלכם אוטומטית לאחר כמה ניסיונות כושלים, ושום דבר לא מודיע לכם שזה קרה.
אשרו קודם, עבדו אחר כך: כתבו את גוף הבקשה לתור, החזירו 200, ובצעו את העבודה האמיתית מחוץ לבקשה.
כתובות המקור
כל הערוצים נשלחים מהכתובות האלה:
46.31.96.138
46.31.96.222
46.31.97.35
46.31.97.108
46.31.97.109
46.31.97.205הגדרתן כרשימת כתובות מורשות בשכבת הקצה היא בדיקת הזהות היחידה שקיימת, מכיוון שהבקשה עצמה אינה נושאת שום פרט גישה.
מה להתיר, את המארחים או את הבלוקים
שש הכתובות יושבות בשני בלוקי /24 סמוכים בשני אתרים, ומכאן שיש שש ולא אחת:
| בלוק | אתר | הכתובות ברשימה |
|---|---|---|
46.31.96.0/24 | Telzar 019 Infrastructure, פתח תקווה | .138, .222 |
46.31.97.0/24 | Telzar 019 Infrastructure, חיפה | .35, .108, .109, .205 |
שניהם רשומים כ-ASSIGNED PA ל-TELZAR-INFRA1, מתוחזקים על ידי Telzar-MNT, בתוך 46.31.96.0/21 שמוכרז על ידי AS51825 עם רשומת RPKI ROA.
מכאן שיש לכם בחירה:
- שש כתובות ה-
/32מדויקות, והכלל ההדוק ביותר שאפשר לכתוב. הן גם מארחים בודדים, ולא מפורסמת תקופת הודעה מוקדמת לשינוי שלהן. - שני בלוקי ה-
/24הם הקצאות רישום. הם אינם זזים אלא אם ההקצאה עצמה משתנה, ולכן הם שורדים החלפת מספור של מארח. המחיר הוא שאתם מקבלים גם מארחי תשתית אחרים של Telzar בבלוקים האלה.
בחרו ב-/32 אם אתם יכולים לחיות עם בדיקה חוזרת של הרשימה, וב-/24 אם שקט בלתי מוסבר בדוחות המסירה שלכם גרוע יותר מכלל מעט רחב יותר.
לא מפורסמת תקופת הודעה מוקדמת לשינוי הרשימה
TextMe אינה מציינת תקופת הודעה מוקדמת לשינוי הכתובות האלה. אם אתם חוסמים לפיהן באופן קשיח, התייחסו לשקט בלתי מוסבר בערוץ כאל סיבה אפשרית, וּודאו מול התמיכה לפני שאתם מסיקים שהתקלה אצלכם.
POST דוחות מסירה
נשלח בכל פעם שסטטוס מסירה נוצר. אותו מידע ש-dlr מחזירה, דוח אחד לכל בקשה.
| שדה | תיאור |
|---|---|
external_id | המזהה שהגדרתם על אלמנט ה-<phone> בזמן השליחה. |
status | סטטוס המסירה. ראו סטטוסי מסירה. |
he_message | הסטטוס בעברית. |
en_message | הסטטוס באנגלית. |
date | מועד רישום הסטטוס, dd/mm/yy hh:mm:ss. |
phone | היעד, בצורה בינלאומית, לדוגמה 9725xxxxxxxx. |
operaor | המפעיל שטיפל בו. מאויית בלי ה-t: ראו את ההערה למטה. |
shipment_id | הקמפיין שההודעה שייכת לו. |
כ-URL, המקבילה נראית כך:
http://your-app.example.com/textme/dlr?external_id=1234&status=102&he_message=הגיע+ליעד&en_message=Delivered
&date=01/04/14 16:05:05&phone=9725xxxxxxxx&operaor=Telzar&shipment_id=xxxxxxxxxoperaor, לא operator
שדה המפעיל מאויית שגוי בגוף ה-Push. תשובת dlr המתושאלת מאייתת אותו operator. קוד שקורא דוחות משני המקורות צריך לקבל את שני האיותים. קריאת operator בלבד מ-Push תחזיר כלום בשקט.
POST הודעות נכנסות
נשלח כשמישהו שולח הודעה לאחד המספרים שלכם. אותו מידע ש-incoming מחזירה.
| שדה | תיאור |
|---|---|
message | הטקסט שהתקבל. |
date | מועד ההגעה, dd/mm/yy hh:mm:ss. |
phone | המספר ששלח אותה. |
dest | המספר שלכם שאליו היא הגיעה. |
http://your-app.example.com/textme/incoming?message=This+is+a+sample+message&date=01/04/14 16:05:05&phone=9725xxxxxxxx&dest=9725xxxxxxxxPOST הוספות לרשימת החסימה
נשלח כשמנוי נחסם. בדרך כלל מפני שביקש להסירו מהודעה שנשאה add_unsubscribe.
| שדה | תיאור |
|---|---|
message | הערה על החסימה, בעברית. לדוגמה נחסם מנוי. |
date | מועד ההתרחשות, dd/mm/yy hh:mm:ss. |
dest | המספר שנחסם. |
http://your-app.example.com/textme/blacklist?message=נחסם+מנוי&date=01/04/14 16:05:05&dest=9725xxxxxxxxזה אות ההסרה שכדאי לחבר קודם
TextMe כבר מדכאת מספרים חסומים בצד שלה. הסיבה לצרוך את הערוץ הזה היא מסד הנתונים שלכם. כדי שאיש הקשר יפסיק לקבל דואר, התראות Push וכל דבר אחר שאתם שולחים, ולא רק SMS. ראו הסרה ורגולציה.
קבלת הודעת Push
אשרו מיד, ואז עבדו. כל דוגמה למטה עושה את אותם שלושה דברים: קוראת את שדות הטופס, מעבירה אותם לתור, ומחזירה 200.
// Express. שימו לב ל-express.urlencoded, לא express.json
import express from 'express'
const app = express()
app.use(express.urlencoded({ extended: false }))
app.post('/textme/dlr', (req, res) => {
const { external_id, status, en_message, date, phone, shipment_id } = req.body
// `operaor` הוא האיות ב-Push; `operator` הוא האיות בדוח המתושאל.
const carrier = req.body.operaor ?? req.body.operator
// אשרו קודם. כל דבר איטי יותר מסתכן במחזור ניסיונות ואז השבתה.
res.sendStatus(200)
queue.push({ external_id, status, en_message, date, phone, carrier, shipment_id })
})
app.post('/textme/incoming', (req, res) => {
const { message, date, phone, dest } = req.body
res.sendStatus(200)
queue.push({ kind: 'incoming', message, date, from: phone, to: dest })
})
app.post('/textme/blacklist', (req, res) => {
const { dest, date } = req.body
res.sendStatus(200)
queue.push({ kind: 'opt-out', phone: dest, date })
})<?php
// PHP רגיל. הגוף הוא form-encoded, ולכן הוא מגיע ל-$_POST.
$report = [
'external_id' => $_POST['external_id'] ?? null,
'status' => $_POST['status'] ?? null,
'he_message' => $_POST['he_message'] ?? null,
'en_message' => $_POST['en_message'] ?? null,
'date' => $_POST['date'] ?? null,
'phone' => $_POST['phone'] ?? null,
// `operaor` הוא האיות ב-Push; `operator` הוא האיות בדוח המתושאל.
'carrier' => $_POST['operaor'] ?? $_POST['operator'] ?? null,
'shipment_id' => $_POST['shipment_id'] ?? null,
];
// אשרו לפני ביצוע עבודה אמיתית.
http_response_code(200);
header('Content-Length: 0');
header('Connection: close');
flush();
queue_delivery_report($report);<?php
// routes/web.php. החריגו את הנתיב מאימות CSRF.
Route::post('/textme/dlr', function (Illuminate\Http\Request $request) {
ProcessDeliveryReport::dispatch([
'external_id' => $request->input('external_id'),
'status' => $request->input('status'),
'en_message' => $request->input('en_message'),
'date' => $request->input('date'),
'phone' => $request->input('phone'),
// `operaor` הוא האיות ב-Push; `operator` הוא האיות המתושאל.
'carrier' => $request->input('operaor', $request->input('operator')),
'shipment_id' => $request->input('shipment_id'),
]);
// בתור, לא מעובד, התשובה חוזרת מיד.
return response()->noContent(200);
});
Route::post('/textme/blacklist', function (Illuminate\Http\Request $request) {
SuppressContact::dispatch($request->input('dest'), $request->input('date'));
return response()->noContent(200);
});# Flask, request.form, לא request.json
from flask import Flask, request
app = Flask(__name__)
@app.post("/textme/dlr")
def delivery_report():
form = request.form
queue.put({
"external_id": form.get("external_id"),
"status": form.get("status"),
"en_message": form.get("en_message"),
"date": form.get("date"),
"phone": form.get("phone"),
# `operaor` הוא האיות ב-Push; `operator` הוא האיות המתושאל.
"carrier": form.get("operaor") or form.get("operator"),
"shipment_id": form.get("shipment_id"),
})
# אשרו מיד; ה-worker עושה את השאר.
return "", 200
@app.post("/textme/blacklist")
def opt_out():
queue.put({"kind": "opt-out", "phone": request.form.get("dest")})
return "", 200package main
import (
"log"
"net/http"
)
func deliveryReport(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
// עדיין מאשרים: תשובה שאינה 200 מתחילה מחזור ניסיונות ואז השבתה.
w.WriteHeader(http.StatusOK)
log.Println("textme: unparsable push:", err)
return
}
// `operaor` הוא האיות ב-Push; `operator` הוא האיות בדוח המתושאל.
carrier := r.FormValue("operaor")
if carrier == "" {
carrier = r.FormValue("operator")
}
report := map[string]string{
"external_id": r.FormValue("external_id"),
"status": r.FormValue("status"),
"en_message": r.FormValue("en_message"),
"date": r.FormValue("date"),
"phone": r.FormValue("phone"),
"carrier": carrier,
"shipment_id": r.FormValue("shipment_id"),
}
w.WriteHeader(http.StatusOK)
go enqueue(report)
}
func main() {
http.HandleFunc("/textme/dlr", deliveryReport)
log.Fatal(http.ListenAndServe(":8080", nil))
}// ASP.NET Core minimal API
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/textme/dlr", async (HttpRequest request, IReportQueue queue) =>
{
var form = await request.ReadFormAsync();
// `operaor` הוא האיות ב-Push; `operator` הוא האיות בדוח המתושאל.
var carrier = form["operaor"].FirstOrDefault() ?? form["operator"].FirstOrDefault();
// מעבירים לתור ולא מעבדים. התשובה לא צריכה לחכות לעבודה.
queue.Enqueue(new DeliveryReport(
ExternalId: form["external_id"],
Status: form["status"],
EnMessage: form["en_message"],
Date: form["date"],
Phone: form["phone"],
Carrier: carrier,
ShipmentId: form["shipment_id"]));
return Results.Ok();
});
app.Run();require "sinatra"
post "/textme/dlr" do
# `operaor` הוא האיות ב-Push; `operator` הוא האיות בדוח המתושאל.
carrier = params["operaor"] || params["operator"]
Queue.push(
external_id: params["external_id"],
status: params["status"],
en_message: params["en_message"],
date: params["date"],
phone: params["phone"],
carrier: carrier,
shipment_id: params["shipment_id"],
)
# אשרו מיד.
status 200
body ""
end
post "/textme/blacklist" do
Queue.push(kind: "opt-out", phone: params["dest"], date: params["date"])
status 200
body ""
end// Spring Boot. @RequestParam קורא שדות form-encoded
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
public class TextMePushController {
private final ReportQueue queue;
public TextMePushController(ReportQueue queue) {
this.queue = queue;
}
@PostMapping("/textme/dlr")
public ResponseEntity<Void> deliveryReport(
@RequestParam(required = false) String external_id,
@RequestParam(required = false) String status,
@RequestParam(required = false) String en_message,
@RequestParam(required = false) String date,
@RequestParam(required = false) String phone,
// `operaor` הוא האיות ב-Push; `operator` הוא האיות המתושאל.
@RequestParam(required = false) String operaor,
@RequestParam(required = false) String operator,
@RequestParam(required = false) String shipment_id) {
String carrier = operaor != null ? operaor : operator;
queue.enqueue(external_id, status, en_message, date, phone, carrier, shipment_id);
// אשרו מיד; התור עושה את העבודה.
return ResponseEntity.ok().build();
}
}הערות על השדות
Push אינו מחליף תשאול לחלוטין
הודעת Push מספרת לכם על אירוע אחד, פעם אחת, ואם הכתובת שלכם הייתה מושבתת במהלך חלון הניסיונות, האירוע הזה נעלם מהערוץ. שמרו על סריקת dlrByDate תקופתית על היום האחרון כרשת ביטחון, ובצעו התאמה לפי external_id. Push נותן לכם השהיה קצרה; תשאול נותן לכם שלמות.
התאריכים מגיעים עם שניות
גוף Push נושא dd/mm/yy hh:mm:ss. רכיב אחד יותר מ-dd/mm/yy hh:mm שאתם שולחים בפרמטרים של בקשות. פרסר שנכתב בקפדנות לפי פורמט הבקשה ידחה אותם.
הערכים מקודדים ב-URL, כולל עברית
he_message=הגיע+ליעד מגיע מקודד באחוזים עם + במקום רווחים. כל פרסר טפסים סטנדרטי מטפל בזה; פיצול ידני על & ועל = לא יטפל.
לזהות ש-Push הפסיק
מכיוון שההשבתה שקטה, מצב הכשל הוא ערוץ שפשוט משתתק, מה שנראה בדיוק כמו יום שקט. התריעו על היעדר הודעות Push: אם קמפיין יצא לפני שעה ואף דוח לא הגיע, הבעיה היא בכתובת ולא בקמפיין.

