---
url: https://textme-docs.matat.io/he/endpoints/push.md
description: >-
  דוחות מסירה, הודעות נכנסות והוספות לרשימת החסימה שנשלחות בבקשת POST לכתובת
  שלכם, עם דוגמאות מקבל בשמונה שפות.
---

# Push API

ההפך משאר התיעוד הזה: במקום שאתם תקראו ל-TextMe, TextMe קוראת לכתובת שבשליטתכם. שלושה ערוצים יכולים להישלח ב-Push (דוחות מסירה, הודעות נכנסות והוספות לרשימת החסימה) מה שמייתר את הצורך לתשאל את [הדוחות](./reports.md) כליל.

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

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

```http
POST https://your-app.example.com/textme/dlr
Content-Type: application/x-www-form-urlencoded
```

כל ערוץ שולח את **אותם שמות שדות כמו בתשובת ה-XML המתושאלת, בצורה שטוחה**: שדות טופס, לא XML ולא JSON. קראו אותם בדיוק כפי שאתם קוראים שליחת טופס HTML.

::: warning שום דבר לא מאמת את הבקשה
הודעת Push אינה נושאת טוקן, חתימה או סוד משותף. כל מי שילמד את הכתובת שלכם יכול לשלוח אליה. הגנו עליה בעצמכם:

* השתמשו בנתיב שלא ניתן לנחש, והתייחסו אליו כאל פרט גישה.
* אם אפשר, הגדירו רשימת כתובות מורשות בשכבת הקצה.
* הפכו את הטיפול לאידמפוטנטי (לפי `external_id` או לפי `phone` + `date`) כי הודעה שנשלחה שוב אינה ניתנת להבחנה מהודעה חדשה.
* אף פעם לא לפעול על סמך Push לבדו במשהו בלתי הפיך; אמתו קודם מול [`dlr`](./reports.md).
  :::

::: danger החזירו `200`, ומהר
כל תשובה שאינה `200 OK` נחשבת כשל. TextMe מחזיקה את ההודעה ומנסה שוב לזמן מה, ואז **מפסיקה לשלוח לכתובת שלכם אוטומטית** לאחר כמה ניסיונות כושלים, ושום דבר לא מודיע לכם שזה קרה.

אשרו קודם, עבדו אחר כך: כתבו את גוף הבקשה לתור, החזירו `200`, ובצעו את העבודה האמיתית מחוץ לבקשה.
:::

## `POST` דוחות מסירה

נשלח בכל פעם שסטטוס מסירה נוצר. אותו מידע ש-[`dlr`](./reports.md) מחזירה, דוח אחד לכל בקשה.

| שדה | תיאור |
|---|---|
| `external_id` | המזהה שהגדרתם על אלמנט ה-`<phone>` בזמן [השליחה](./send.md#external-ids-and-delivery-reports). |
| `status` | סטטוס המסירה. ראו [סטטוסי מסירה](../reference/dlr-statuses.md). |
| `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
```

::: warning `operaor`, לא `operator`
שדה המפעיל מאויית שגוי בגוף ה-Push. תשובת [`dlr`](./reports.md) המתושאלת מאייתת אותו `operator`. קוד שקורא דוחות משני המקורות צריך לקבל את שני האיותים. קריאת `operator` בלבד מ-Push תחזיר כלום בשקט.
:::

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

נשלח כשמישהו שולח הודעה לאחד המספרים שלכם. אותו מידע ש-[`incoming`](./reports.md) מחזירה.

| שדה | תיאור |
|---|---|
| `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` הוספות לרשימת החסימה {#post-blocklist-additions}

נשלח כשמנוי נחסם. בדרך כלל מפני שביקש להסירו מהודעה שנשאה [`add_unsubscribe`](./send.md#opt-out-footers).

| שדה | תיאור |
|---|---|
| `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
```

::: tip זה אות ההסרה שכדאי לחבר קודם
TextMe כבר מדכאת מספרים חסומים בצד שלה. הסיבה לצרוך את הערוץ הזה היא מסד הנתונים *שלכם*. כדי שאיש הקשר יפסיק לקבל דואר, התראות Push וכל דבר אחר שאתם שולחים, ולא רק SMS. ראו [הסרה ורגולציה](../use-cases/opt-out-and-compliance.md).
:::

## קבלת הודעת Push

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

::: code-group

```js [JavaScript]
// 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
// 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 [Laravel]
<?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 [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 [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 [C#]
// 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 [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 [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`](./reports.md) תקופתית על היום האחרון כרשת ביטחון, ובצעו התאמה לפי `external_id`. Push נותן לכם השהיה קצרה; תשאול נותן לכם שלמות.

### התאריכים מגיעים עם שניות

גוף Push נושא `dd/mm/yy hh:mm:ss`. רכיב אחד יותר מ-`dd/mm/yy hh:mm` שאתם *שולחים* בפרמטרים של בקשות. פרסר שנכתב בקפדנות לפי פורמט הבקשה ידחה אותם.

### הערכים מקודדים ב-URL, כולל עברית

`he_message=הגיע+ליעד` מגיע מקודד באחוזים עם `+` במקום רווחים. כל פרסר טפסים סטנדרטי מטפל בזה; פיצול ידני על `&` ועל `=` לא יטפל.

### לזהות ש-Push הפסיק

מכיוון שההשבתה שקטה, מצב הכשל הוא ערוץ שפשוט משתתק, מה שנראה בדיוק כמו יום שקט. התריעו על *היעדר* הודעות Push: אם קמפיין יצא לפני שעה ואף דוח לא הגיע, הבעיה היא בכתובת ולא בקמפיין.
