# Handle patient replies

> Use BloomText webhooks to turn patient replies into actions, like confirming an appointment in your EMR.

Source: https://www.bloomtext.com/developers/api/guides/handle-replies/

When a patient answers a reminder with "C", you can confirm the appointment in your EMR automatically and let staff see it was handled. This guide wires a [webhook](https://www.bloomtext.com/developers/api/webhooks/) to that action.

**You'll need:** an app user with `messages:read` and `reactions:write`, a webhook endpoint subscribed to `conversation.message.created`, and the reminder job from [Send appointment reminders](https://www.bloomtext.com/developers/api/guides/appointment-reminders/).

### Receive and verify the event

Start from the [verified receiver](https://www.bloomtext.com/developers/api/webhooks/#verify-signatures). Acknowledge fast and queue the event.

### Fetch the message

The event carries IDs only, so read the latest messages to get the text:

```bash filename="Request"
curl "https://api.bloomtext.com/v1/conversations/e5c3b7b8-8f08-4d5a-9af1-0d11b0f4b7a0/messages?page[limit]=5" \
  -H "Authorization: Bearer $BLOOMTEXT_API_KEY"
```

### Match the reply and act

```js filename="handle-reply.mjs"
const API = 'https://api.bloomtext.com/v1'
const headers = { Authorization: `Bearer ${process.env.BLOOMTEXT_API_KEY}` }

export async function handleReply(event, { findPendingReminder, confirmInEmr, alreadyProcessed }) {
  if (await alreadyProcessed(event.id)) return // webhooks can repeat

  const { conversation_id, message_id, sender_id } = event.payload
  if (sender_id === process.env.BLOOMTEXT_APP_USER_ID) return

  const { data } = await fetch(`${API}/conversations/${conversation_id}/messages?page[limit]=5`, { headers })
    .then((r) => r.json())
  const message = data.find((m) => m.id === message_id)
  const answer = message?.body?.trim().toUpperCase()

  const reminder = await findPendingReminder(conversation_id)
  if (!reminder || answer !== 'C') return // anything else is for staff

  await confirmInEmr(reminder.appointmentId)
  await fetch(`${API}/messages/${message_id}/reactions`, {
    method: 'POST',
    headers: { ...headers, 'Idempotency-Key': `confirm:${message_id}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ emoji: '👍' }),
  })
}
```

```python filename="handle_reply.py"
import os

import requests

API = "https://api.bloomtext.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BLOOMTEXT_API_KEY']}"}


def handle_reply(event, find_pending_reminder, confirm_in_emr, already_processed):
    if already_processed(event["id"]):
        return  # webhooks can repeat

    payload = event["payload"]
    if payload["sender_id"] == os.environ["BLOOMTEXT_APP_USER_ID"]:
        return

    messages = requests.get(
        f"{API}/conversations/{payload['conversation_id']}/messages",
        headers=HEADERS,
        params={"page[limit]": 5},
    ).json()["data"]
    message = next((m for m in messages if m["id"] == payload["message_id"]), None)
    answer = (message or {}).get("body", "").strip().upper()

    reminder = find_pending_reminder(payload["conversation_id"])
    if not reminder or answer != "C":
        return  # anything else is for staff

    confirm_in_emr(reminder["appointment_id"])
    requests.post(
        f"{API}/messages/{payload['message_id']}/reactions",
        headers={**HEADERS, "Idempotency-Key": f"confirm:{payload['message_id']}"},
        json={"emoji": "👍"},
    ).raise_for_status()
```

### Let staff see what happened

The 👍 reaction shows staff the reply was handled automatically. Anything you don't recognize stays unreacted, so it stands out for a person to answer.

> **Note:** Keep the matching strict. "C" confirms; "can I come at 4 instead?" doesn't. When in doubt, leave it for staff.
