Push API
ההפך משאר התיעוד הזה: במקום שאתם תקראו ל-TextMe, TextMe קוראת לכתובת שבשליטתכם. שלושה ערוצים יכולים להישלח ב-Push — דוחות מסירה, הודעות נכנסות והוספות לרשימת החסימה — מה שמייתר את הצורך לתשאל את הדוחות כליל.
אתם מספקים את הכתובת ל-TextMe; אין פעולת API לרישום שלה.
איך הודעת Push מגיעה
POST https://your-app.example.com/textme/dlr
Content-Type: application/x-www-form-urlencodedכל ערוץ שולח את אותם שמות שדות כמו בתשובת ה-XML המתושאלת, בצורה שטוחה — שדות טופס, לא XML ולא JSON. קראו אותם בדיוק כפי שאתם קוראים שליחת טופס HTML.
שום דבר לא מאמת את הבקשה
הודעת Push אינה נושאת טוקן, חתימה או סוד משותף. כל מי שילמד את הכתובת שלכם יכול לשלוח אליה. הגנו עליה בעצמכם:
- השתמשו בנתיב שלא ניתן לנחש, והתייחסו אליו כאל פרט גישה.
- אם אפשר, הגדירו רשימת כתובות מורשות בשכבת הקצה.
- הפכו את הטיפול לאידמפוטנטי — לפי
external_idאו לפיphone+date— כי הודעה שנשלחה שוב אינה ניתנת להבחנה מהודעה חדשה. - אף פעם לא לפעול על סמך Push לבדו במשהו בלתי הפיך; אמתו קודם מול
dlr.
החזירו 200, ומהר
כל תשובה שאינה 200 OK נחשבת כשל. TextMe מחזיקה את ההודעה ומנסה שוב לזמן מה, ואז מפסיקה לשלוח לכתובת שלכם אוטומטית לאחר כמה ניסיונות כושלים — ושום דבר לא מודיע לכם שזה קרה.
אשרו קודם, עבדו אחר כך: כתבו את גוף הבקשה לתור, החזירו 200, ובצעו את העבודה האמיתית מחוץ לבקשה.
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: אם קמפיין יצא לפני שעה ואף דוח לא הגיע, הבעיה היא בכתובת ולא בקמפיין.

