# Abstra Hooks — Examples

Each block below is a **complete hook stage file**. See `hooks_guide.md` for the API. Hooks run top-to-bottom per request; always end with a response.

## 1. Minimal webhook — accept, hand off, acknowledge

```python
# Receive any JSON POST, queue the work for a tasklet, ack immediately.
import abstra.hooks as ah
from abstra.tasks import send_task

body, query, headers = ah.get_request()   # body is already parsed (dict for JSON)

# Don't do heavy work here — offload it so we answer the caller fast.
send_task("received_request", {"body": body})

ah.send_json({"ok": True}, status_code=200)
```

## 2. Verified webhook — HMAC on the raw body, idempotent, async hand-off

```python
# Pattern for Stripe/GitHub-style webhooks: verify the signature over the RAW
# bytes, dedupe on the delivery id, queue the work, then 200 fast.
import os, hmac, hashlib, json
import abstra.hooks as ah
import abstra.tables as at
from abstra.tasks import send_task
from abstra.logging import logger

# Verify on the RAW body — get_request() would re-parse and change the bytes.
raw_body, query, headers = ah.get_raw_request()

# Header names may be normalized to lowercase — look them up lowercased.
signature = headers.get("x-hub-signature-256", "")
secret = os.environ["GITHUB_WEBHOOK_SECRET"]
expected = "sha256=" + hmac.new(secret.encode(), raw_body.encode(), hashlib.sha256).hexdigest()

if not hmac.compare_digest(expected, signature):
    logger.warning("Webhook rejected: bad signature")
    ah.send_json({"error": "invalid signature"}, status_code=401)
else:
    delivery_id = headers.get("x-github-delivery", "")
    # Idempotency: senders retry, so skip anything we've already accepted.
    if at.select_one("webhook_deliveries", where={"delivery_id": delivery_id}):
        ah.send_json({"ok": True, "duplicate": True}, status_code=200)
    else:
        at.insert("webhook_deliveries", {"delivery_id": delivery_id})
        event = json.loads(raw_body)
        send_task("github_event", {"action": event.get("action"), "delivery_id": delivery_id})
        ah.send_json({"ok": True}, status_code=200)
```

## 3. Connector-event webhook — auto-routed by hook path

```python
# Name this hook's path `stripe/charge-succeeded` (connection-name/event-name,
# kebab-case) and it automatically receives that connector's events. The event
# payload arrives as the request body — no signature handling needed.
import abstra.hooks as ah
from abstra.tasks import send_task
from abstra.logging import logger

charge, query, headers = ah.get_request()   # body = the Stripe charge object

logger.info(f"Charge succeeded: {charge.get('id')} for {charge.get('amount')}")
send_task("payment_received", {
    "charge_id": charge.get("id"),
    "amount": charge.get("amount"),
    "customer": charge.get("customer"),
})
ah.send_json({"ok": True})
```

## 4. Public JSON API endpoint — synchronous request/response

```python
# A lightweight read endpoint: GET /_hooks/order-status?order_id=123
import abstra.hooks as ah
import abstra.tables as at

body, query, headers = ah.get_request()
order_id = query.get("order_id")

if not order_id:
    ah.send_json({"error": "order_id is required"}, status_code=400)
else:
    order = at.select_one("orders", where={"id": order_id})
    if order is None:
        ah.send_json({"error": "not found"}, status_code=404)
    else:
        ah.send_json({"id": order["id"], "status": order["status"]}, status_code=200)
```

## 5. Inbound email ingestion — save attachments, queue processing

```python
# Trigger by emailing  <subdomain>+<hook-path>@abstra-notifications.com
# e.g. invoices arriving by email; persist each attachment and queue it.
import base64
import abstra.hooks as ah
from abstra.common import get_persistent_dir
from abstra.tasks import send_task
from abstra.logging import logger

email = ah.get_email_request()   # MailParser: .body .subject .from_ .to .attachments
logger.info(f"Email from {email.from_} — {email.subject}")

for attachment in email.attachments:
    filename = attachment.get("filename")
    data = base64.b64decode(attachment.get("payload"))

    path = get_persistent_dir() / filename
    with open(path, "wb") as f:
        f.write(data)

    # Hand the saved file to a tasklet for parsing/processing.
    send_task("invoice_received", {"path": str(path), "sender": email.from_})
```

## 6. Callback returning HTML / a redirect — `send_response`

```python
# Payment/form provider redirects the user back here; show a page or 302.
import abstra.hooks as ah
import abstra.tables as at

body, query, headers = ah.get_request()
checkout_id = query.get("checkout_id")
status = (query.get("status") or "").lower()

at.update("checkouts", set={"status": status}, where={"id": checkout_id})

if status == "paid":
    # Redirect the browser to your app's success page.
    ah.send_response("", status_code=302, headers={"Location": "https://app.example.com/thanks"})
else:
    html = f"<html><body><h1>Payment {status}</h1><p>Ref {checkout_id}</p></body></html>"
    ah.send_response(html, status_code=200, headers={"Content-Type": "text/html; charset=utf-8"})
```
