# Day Day Help — "Get my documents" API

What Day Day Help's endpoint must accept and answer so a Plantoo chat can fetch a customer's
documents. Draft 2, 30/09/2026 — a not-found answer now carries `"found": false`.

## How it is used

1. A chatflow step shows the customer a button, e.g. **Get my documents**.
2. The customer presses it once. Plantoo's **server** (not their browser) sends one POST to your
   endpoint.
3. Your endpoint answers **at once** with the documents (any number of them).
4. Plantoo stores them on the case and sends them to the customer in the chat.

If the call fails, the customer can press the button again, up to the number of tries set on the
step. After that the chat waits for the organization's staff, who can ask again from their Inbox
as often as needed (for example once your endpoint is fixed).

## Settings (in Plantoo: Calculations → Your document source)

| Setting | Notes |
|---|---|
| **Address** | Your endpoint. Must be `https://`. |
| **Secret** | Shown in Plantoo. Keep it on your server only; it signs every request. |

## The request

```
POST {your address}
Content-Type: application/json
X-Plantoo-Timestamp: 1790723456
X-Plantoo-Signature: sha256=5f2c…(64 hex characters)
X-Plantoo-Request-Id: 6abc6f87-1-3f9a2c
```

```json
{
  "reference": "TBTPN-VK8RG-48AWE-QGNG5",
  "request_id": "6abc6f87-1-3f9a2c",
  "attempt": 1,
  "fields": {
    "contract_no": "C-2026-0419",
    "email": "customer@example.com",
    "whatsapp": "+85291234567"
  }
}
```

| Field | Meaning |
|---|---|
| `reference` | This conversation's reference. Quote it in your logs; it is how a support question is traced. |
| `request_id` | One per press of the button. **The same `request_id` may arrive twice** (a network retry) — answer it the same way both times. |
| `attempt` | 1 for the first press, 2 for the second, … |
| `fields` | Exactly the name → value pairs configured on the chatflow step, filled in for this customer. You choose the names when the step is set up. Empty values arrive as `""`. |

### Checking the request is from Plantoo — do this first

1. Refuse if `X-Plantoo-Timestamp` is more than **5 minutes** from your server's time.
2. Compute `HMAC-SHA256(secret, timestamp + "." + raw_request_body)` as lowercase hex.
3. Compare it with the part of `X-Plantoo-Signature` after `sha256=`, using a **constant-time**
   comparison. Refuse on any difference.

Use the **raw body exactly as received**, before any JSON parsing.

```php
$timestamp = $request->header("X-Plantoo-Timestamp");
$given     = substr((string) $request->header("X-Plantoo-Signature"), 7); // after "sha256="
$expected  = hash_hmac("sha256", $timestamp.".".$request->getContent(), config("services.plantoo.secret"));

if (abs(time() - (int) $timestamp) > 300 || !hash_equals($expected, $given)) {
    return response()->json(["message" => "Signature check failed."], 401);
}
```

## The answer

Answer within **20 seconds**. Always JSON.

### Documents found — `200`

```json
{
  "customer_ref": "DDH-10234",
  "message": "Here are your contract and your ID407.",
  "documents": [
    { "name": "Employment contract.pdf", "mime": "application/pdf", "content": "JVBERi0xLjcK…" },
    { "name": "ID407.pdf", "mime": "application/pdf", "url": "https://…/signed/abc?expires=…",
      "sha256": "9b1d…" }
  ]
}
```

| Field | Required | Meaning |
|---|---|---|
| `documents` | yes | 1 or more. |
| `documents[].name` | yes | The file name the customer sees, with its extension. Up to 120 characters. |
| `documents[].mime` | yes | `application/pdf`, `image/png` or `image/jpeg`. |
| `documents[].content` | one of these two | The file itself, base64. |
| `documents[].url` | one of these two | An `https://` link Plantoo downloads **straight away**, with a plain GET and no sign-in (a signed, expiring link is ideal). Must stay valid for at least **10 minutes**. |
| `documents[].sha256` | no | Hex checksum of the file. If given, Plantoo refuses a file that does not match it. |
| `customer_ref` | no | Your own ID for this customer. Plantoo keeps it on the case, so a later step can send it back to you as a field. |
| `message` | no | One sentence the customer sees above the documents. |

### Nothing to send — `404`

```json
{ "found": false, "message": "We could not find a contract for that number." }
```

Use this when the customer is not found or has no documents ready. **`"found": false` is required**:
a 404 without it is read as "this address does not exist" (a wrong or undeployed route answers 404
too) — it counts as a failed try, and nothing you wrote is shown. `message` is shown to the customer,
so write it for them. The chat takes its "not found" branch.

### The request itself is wrong — `422`

```json
{ "found": false, "message": "contract_no is missing." }
```

For a field that is missing or malformed. Treated like "not found", and the message is shown to
the customer — so keep it readable.

### Signature refused — `401`

Plantoo treats this as a set-up problem: nothing is shown but a general error, and the attempt counts
towards the tries.

### Anything else

A `5xx`, no answer within 20 seconds, or a body that is not JSON counts as **"didn't answer"**: the
customer is told to try again, and the attempt counts towards the tries.

## Limits

| Limit | Value |
|---|---|
| Time to answer | 20 seconds |
| Documents per answer | 10 |
| Size of one document | 10 MB |
| Whole answer | 25 MB (base64 makes files a third bigger — prefer `url` for large ones) |
| File types | PDF, PNG, JPEG |

A document that breaks a limit, cannot be downloaded or fails its checksum is left out; the others
are still delivered and the case records which one failed. If every document fails, it counts as
"didn't answer".

## Security

- **Only ever return the documents of the customer the fields identify.** Match on more than one
  field (e.g. contract number **and** WhatsApp number) before answering with anything.
- Keep the secret on your server. Never put it in a page, a script a browser loads, or a log.
- Documents may contain HKID numbers and wages: log that a request was answered, not what was in it.

## Testing checklist

| Send | Expect |
|---|---|
| A valid request for a customer with two documents | `200` with both |
| The same `request_id` again | the same `200` |
| A contract number that does not exist | `404` with `"found": false` and a customer-facing message |
| `fields` without `contract_no` | `422` |
| A wrong signature, or a timestamp 10 minutes old | `401` |
