Skip to content

Push API

ההפך משאר התיעוד הזה: במקום שאתם תקראו ל-TextMe, TextMe קוראת לכתובת שבשליטתכם. שלושה ערוצים יכולים להישלח ב-Push — דוחות מסירה, הודעות נכנסות והוספות לרשימת החסימה — מה שמייתר את הצורך לתשאל את הדוחות כליל.

אתם מספקים את הכתובת ל-TextMe; אין פעולת API לרישום שלה.

איך הודעת Push מגיעה

http
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, המקבילה נראית כך:

apache
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=xxxxxxxxx

operaor, לא operator

שדה המפעיל מאויית שגוי בגוף ה-Push. תשובת dlr המתושאלת מאייתת אותו operator. קוד שקורא דוחות משני המקורות צריך לקבל את שני האיותים — קריאת operator בלבד מ-Push תחזיר כלום בשקט.

POST הודעות נכנסות

נשלח כשמישהו שולח הודעה לאחד המספרים שלכם. אותו מידע ש-incoming מחזירה.

שדהתיאור
messageהטקסט שהתקבל.
dateמועד ההגעה, dd/mm/yy hh:mm:ss.
phoneהמספר ששלח אותה.
destהמספר שלכם שאליו היא הגיעה.
apache
http://your-app.example.com/textme/incoming?message=This+is+a+sample+message&date=01/04/14 16:05:05&phone=9725xxxxxxxx&dest=9725xxxxxxxx

POST הוספות לרשימת החסימה

נשלח כשמנוי נחסם — בדרך כלל מפני שביקש להסירו מהודעה שנשאה add_unsubscribe.

שדהתיאור
messageהערה על החסימה, בעברית — לדוגמה נחסם מנוי.
dateמועד ההתרחשות, dd/mm/yy hh:mm:ss.
destהמספר שנחסם.
apache
http://your-app.example.com/textme/blacklist?message=נחסם+מנוי&date=01/04/14 16:05:05&dest=9725xxxxxxxx

זה אות ההסרה שכדאי לחבר קודם

TextMe כבר מדכאת מספרים חסומים בצד שלה. הסיבה לצרוך את הערוץ הזה היא מסד הנתונים שלכם — כדי שאיש הקשר יפסיק לקבל דואר, התראות Push וכל דבר אחר שאתם שולחים, ולא רק SMS. ראו הסרה ורגולציה.

קבלת הודעת Push

אשרו מיד, ואז עבדו. כל דוגמה למטה עושה את אותם שלושה דברים: קוראת את שדות הטופס, מעבירה אותם לתור, ומחזירה 200.

js
// 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
// 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
<?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);
});
python
# 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 "", 200
go
package 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))
}
csharp
// 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();
ruby
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
java
// 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: אם קמפיין יצא לפני שעה ואף דוח לא הגיע, הבעיה היא בכתובת ולא בקמפיין.