> ## Documentation Index
> Fetch the complete documentation index at: https://withkazi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SMS Delivery Receipts: DLR Webhook Reference

> LaraCopilot receives delivery receipt (DLR) callbacks from SMS gateways to update message status. Set up your callback URL when sending messages.

When LaraCopilot sends an SMS it registers a callback URL with the gateway. Once the carrier confirms delivery (or failure), the gateway POSTs a delivery receipt — commonly called a DLR — back to that URL. LaraCopilot handles the incoming DLR, looks up the corresponding `SmsMessage` record by its external message ID, and updates both `delivery_status` and `delivered_at`. No further action is required on your side: the status is immediately visible on the message record.

***

## DLR webhook endpoint

LaraCopilot exposes two equivalent URLs that accept inbound DLR callbacks. Both resolve to the same handler logic.

| Route                                     | Description                                                |
| ----------------------------------------- | ---------------------------------------------------------- |
| `POST /api/sms-gateways/{smsGateway}/dlr` | REST API route — recommended for new integrations.         |
| `POST /webhooks/jasmin/{smsGateway}/dlr`  | Web route alias retained for Jasmin gateway compatibility. |

The `{smsGateway}` path segment is the integer ID of the gateway record in LaraCopilot. You typically configure this URL directly in your gateway or include it as the `callback_url` when calling [POST /api/sms-gateways/{id}/send](/api/sms-gateways#send-an-sms).

```http theme={null}
POST /api/sms-gateways/{smsGateway}/dlr
```

**Path parameters**

<ParamField path="smsGateway" type="integer" required>
  The ID of the SMS gateway that is delivering the receipt.
</ParamField>

**Payload fields accepted**

LaraCopilot accepts the DLR payload as either JSON or form-encoded data. It reads the following fields:

<ParamField body="message_id" type="string">
  The external message identifier returned by the gateway when the SMS was submitted. Also accepted as `id` or `messageId` — LaraCopilot checks all three in that order.
</ParamField>

<ParamField body="status" type="string">
  Delivery status string from the gateway. Also accepted as `message_status`. When neither field is present, LaraCopilot defaults the status to `"delivered"`.
</ParamField>

**What happens**

LaraCopilot queries `SmsMessage` records for a row matching both `sms_gateway_id` and `external_message_id`. If a match is found it performs the following updates on that record:

* `delivery_status` — set to the lowercased value of `status` or `message_status` from the payload (default: `"delivered"`).
* `delivered_at` — set to the current timestamp.
* `remote_response` — the inbound DLR payload is merged into the existing `remote_response` object so the full gateway conversation is preserved.

If no matching record is found, LaraCopilot returns `200 OK` with `{"message": "DLR received"}` and takes no further action.

**Response**

```json theme={null}
{
  "message": "DLR received"
}
```

The endpoint always returns HTTP `200`. Gateway retry logic based on non-`2xx` responses will not be triggered.

***

## Configuring the callback URL

When you call [POST /api/sms-gateways/{id}/send](/api/sms-gateways#send-an-sms), include `callback_url` in the request body to tell LaraCopilot where the gateway should send the DLR.

```bash title="Send with explicit callback URL" theme={null}
curl -X POST https://your-instance.laracopilot.com/api/sms-gateways/1/send \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "to_number": "+447911123456",
    "from_sender": "LaraCopilot",
    "message": "Hello from LaraCopilot.",
    "callback_url": "https://your-instance.laracopilot.com/api/sms-gateways/1/dlr"
  }'
```

When `callback_url` is omitted, LaraCopilot automatically sets it to:

```
https://your-instance.laracopilot.com/api/sms-gateways/{id}/dlr
```

This default means the DLR will always be routed back correctly as long as your LaraCopilot instance is publicly reachable by the gateway.

***

## Message status values

`delivery_status` on an `SmsMessage` record moves through the following states:

| Status      | When it is set                                                                      |
| ----------- | ----------------------------------------------------------------------------------- |
| `queued`    | Immediately when the send request is received, before the gateway is contacted.     |
| `submitted` | After the gateway responds with a success status.                                   |
| `delivered` | After a DLR callback confirms the message reached the handset.                      |
| `failed`    | When the gateway returns an error, the request times out, or a DLR reports failure. |

<Note>
  The `status` value sent in the DLR payload is lowercased and written directly to `delivery_status`. If your gateway sends values such as `"DELIVRD"` or `"UNDELIV"`, those exact lowercased strings will appear on the record.
</Note>

***

## Example DLR payload

The following is a representative payload that a Jasmin-compatible gateway sends to the callback URL. Your gateway documentation should confirm the exact fields it uses.

```json title="Example DLR payload from gateway" theme={null}
{
  "message_id": "gw-msg-00993",
  "status": "delivered",
  "to": "+447911123456",
  "from": "LaraCopilot",
  "submit_date": "2024-03-20T12:00:01Z",
  "done_date": "2024-03-20T12:00:08Z"
}
```

LaraCopilot extracts `message_id` and `status` from this payload. All other fields are merged into `remote_response` on the matching `SmsMessage` record for your records.

***

## Full SMS lifecycle

<Steps>
  <Step title="Send request">
    Your application calls `POST /api/sms-gateways/{id}/send`. LaraCopilot creates an `SmsMessage` with `delivery_status: "queued"`.
  </Step>

  <Step title="Gateway dispatch">
    LaraCopilot forwards the message to the gateway over HTTP(S) with Basic Auth. The `callback_url` (your DLR endpoint) is included in the payload as `dlr-url`.
  </Step>

  <Step title="Submitted">
    On a successful gateway response, `delivery_status` is updated to `"submitted"` and `submitted_at` is recorded. The send API returns the message object at this point.
  </Step>

  <Step title="DLR callback">
    The gateway POSTs a delivery receipt to your `callback_url`. LaraCopilot matches the `message_id`, updates `delivery_status` to `"delivered"` (or `"failed"`), and sets `delivered_at`.
  </Step>
</Steps>

***

## Security: validating callback\_token

<Warning>
  Without token validation, any party that knows your DLR URL can forge delivery receipts and manipulate message status records.
</Warning>

Each gateway record has a `callback_token` field. When configuring your gateway in LaraCopilot, set a long random string as the token and configure your gateway to include it in every DLR request — typically as a query parameter or custom header.

Your gateway configuration or a reverse-proxy rule should reject DLR requests that do not carry the expected token before they reach LaraCopilot. The `callback_token` value is available on the gateway object returned by [GET /api/sms-gateways/{id}](/api/sms-gateways#get-a-gateway).

```bash title="Retrieve a gateway's callback_token" theme={null}
curl https://your-instance.laracopilot.com/api/sms-gateways/1 \
  -b cookies.txt
```
