Skip to content

docs: document the Day Summary webhook and standardize created_at on UTC - #7110

Merged
syou6162 merged 2 commits into
BasedHardware:mainfrom
syou6162:doc/day_summary_webhook
May 30, 2026
Merged

docs: document the Day Summary webhook and standardize created_at on UTC#7110
syou6162 merged 2 commits into
BasedHardware:mainfrom
syou6162:doc/day_summary_webhook

Conversation

@syou6162

@syou6162 syou6162 commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Documents the previously-undocumented Day Summary webhook on the public Integrations page, and aligns the top-level created_at it sends with the rest of the backend (UTC ISO 8601 with +00:00 offset).

This branch was originally larger and proposed flipping the summary field from a Python repr string to a real JSON object as well. Per @beastoin's review feedback we've split that work off — this PR is now intentionally docs-leaning, ships the small created_at consistency fix, and leaves the wire-format question for a separate follow-up.

What's in this PR

  • backend/utils/webhooks.pyday_summary_webhook created_at is now datetime.now(timezone.utc).isoformat(). That was the only place in backend/ still calling naive datetime.now() for a wire-format timestamp; every other timestamp the backend emits (utils/social.py, utils/chat.py, routers/users.py, …) already uses datetime.now(timezone.utc). The payload now sends "2024-01-15T22:00:00.123456+00:00" instead of the naive "2024-01-15T22:00:00.123456" so receivers can parse it unambiguously.
  • docs/doc/developer/apps/Integrations.mdx — new "Day Summary" section covering:
    • Endpoint shape (POST /your-endpoint?uid=...) and a JSON example reflecting the current payload
    • A <Warning> block that's blunt about the current wire format: summary is a Python repr string (single-quoted, not JSON) rather than a JSON object, so receivers cannot use JSON.parse and currently need a Python-specific parser such as ast.literal_eval to extract structured fields. A follow-up PR will add a dedicated JSON-object field for the summary
    • A schema snapshot of the underlying summary dict (the shape the future JSON-object field will use) so receivers can plan against it
    • "Delivery conditions" listing the cases where the webhook does not fire (no conversations for the day, all conversations locked / no transcribed speech, no FCM token registered, atomic 2-hour (uid, date) Redis lock already held)
    • Timezone requirement scoped to the scheduled cron path; the manual "Generate Summary" trigger falls back to UTC day boundaries
    • A <Note> clarifying that the in-app "Generate Summary" action regenerates the summary but does not currently POST to the developer webhook, so the recommended validation path is still to wait for the next scheduled cron tick
  • Page-level integration of the new section: the intro CardGroup is recomposed to cols={2} 2×2 so all four trigger types fit, a Day Summary card is added, the mermaid diagram gets a Day Summary node, "Choose Your Trigger Type" and "Set Webhook URL" steps now cover all four trigger types (also fills the previous Audio Bytes gap), and the frontmatter description mentions daily summaries.

What's deliberately not in this PR

  • No change to the summary field on the wire. It is still a Python repr string; the docs describe that as the current state and warn receivers accordingly.
  • No new summary_json (or similarly named) field. That belongs to the follow-up breaking-change PR per @beastoin's note ("you can also add a new field that returns the JSON object for the summary — that should be done in a breaking change PR"). Tracked separately so this docs-leaning PR can land without coupling to that decision.

Why the created_at change is in scope

@beastoin's review asked that "the date format should be updated to make it consistent". The only date format this PR touches is the top-level webhook created_at — moving it from naive local-server time to a +00:00 UTC ISO 8601 string. Pinging if a different field was meant: happy to revert this hunk and ship a docs-only PR instead.

Migration impact

None for the summary field — its shape and serialization are unchanged.

The top-level created_at switches from a naive ISO 8601 string ("2024-01-15T22:00:00.123456") to an offset-suffixed one ("2024-01-15T22:00:00.123456+00:00"). Standard parsers (Date.parse, datetime.fromisoformat, Dart's DateTime.parse, …) accept both forms, and the new form removes the ambiguity around which timezone the naive string was in (it was always UTC in practice — only the explicit suffix changes).

Test plan

  • black --check --line-length 120 --skip-string-normalization backend/utils/webhooks.py — clean
  • Manual review of the rendered MDX section structure (CardGroup, mermaid, AccordionGroup, payload + schema blocks, Warning, Note)
  • Mintlify preview to confirm the new section, the 2×2 CardGroup layout, the mermaid render, and the Warning / Note styling — reviewer help appreciated, as no local Mintlify is available in this dev env

@syou6162 syou6162 self-assigned this May 1, 2026
@syou6162 syou6162 changed the title BREAKING: document Day Summary webhook and serialize summary as JSON object docs: document Day Summary webhook and serialize summary as JSON object May 1, 2026
@syou6162
syou6162 marked this pull request as ready for review May 1, 2026 10:20
@syou6162
syou6162 requested a review from beastoin May 1, 2026 10:20
@greptile-apps

greptile-apps Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the day_summary webhook payload by removing the str() wrap around summary_data so receivers get a proper JSON object instead of a Python repr string, and updates the function signature accordingly (summary: strsummary: dict). It also ships the first public documentation for the Day Summary webhook, including a payload spec, field reference, and a breaking-change notice for existing receivers.

Confidence Score: 4/5

Safe to merge; the one-line fix is correct and well-tested, with only minor test and doc polish remaining.

All findings are P2 (style/best-practices): thinner test fixture schema than documented, MagicMock instead of AsyncMock for an async patch, and a missing UTC offset note in the docs example. No logic bugs or security issues introduced.

backend/tests/unit/test_async_webhooks.py — AsyncMock vs MagicMock for the wiring test and _SAMPLE_SUMMARY completeness.

Important Files Changed

Filename Overview
backend/utils/other/notifications.py Drops the str() wrap around summary_data before passing to day_summary_webhook, so the dict is now forwarded directly — the core correctness fix in this PR.
backend/utils/webhooks.py Updates day_summary_webhook signature from summary: str to summary: dict; the json= payload now sends the dict directly so receivers get a real JSON object.
backend/tests/unit/test_async_webhooks.py Adds TestDaySummaryWebhook (success, disabled, circuit-breaker paths) and TestSendSummaryNotificationWiring (dict-identity guard); _SAMPLE_SUMMARY action-items schema is thinner than the documented payload, and the wiring test patches an async function with a plain MagicMock rather than AsyncMock.
docs/doc/developer/apps/Integrations.mdx Comprehensive new "Day Summary" section with How It Works, Webhook Payload, field reference table, and breaking-change Note; top-level created_at example lacks UTC offset, which could mislead receivers.

Sequence Diagram

sequenceDiagram
    participant Cron as Hourly Cron Job
    participant N as notifications.py<br/>_send_summary_notification
    participant LLM as generate_comprehensive<br/>_daily_summary
    participant DB as daily_summaries_db
    participant Exec as storage_executor
    participant W as webhooks.py<br/>day_summary_webhook
    participant Recv as Your Webhook Endpoint

    Cron->>N: _send_summary_notification(user_data)
    N->>LLM: generate_comprehensive_daily_summary(...)
    LLM-->>N: summary_data (dict)
    N->>DB: create_daily_summary(uid, summary_data)
    DB-->>N: summary_id
    N->>Exec: submit(asyncio.run, day_summary_webhook(uid, summary_data))
    Note over N,Exec: Previously str(summary_data) — now passes dict directly
    Exec->>W: asyncio.run(coroutine)
    W->>W: Check webhook enabled and circuit breaker
    W->>Recv: POST /your-endpoint?uid=... with JSON summary object
    Recv-->>W: 200 OK
Loading

Reviews (1): Last reviewed commit: "docs: list summary as a top-level field ..." | Re-trigger Greptile

Comment thread backend/tests/unit/test_async_webhooks.py Outdated
Comment thread backend/tests/unit/test_async_webhooks.py Outdated
Comment thread docs/doc/developer/apps/Integrations.mdx Outdated
@syou6162
syou6162 requested a review from mdmohsin7 May 1, 2026 10:47
@syou6162

syou6162 commented May 7, 2026

Copy link
Copy Markdown
Collaborator Author

@beastoin @mdmohsin7 Could you review this pull request?

The main question is whether the breaking change to the wire format is OK to land together with the docs. If you'd rather keep the docs and the fix separate, I'm happy to split this into:

  • A docs-only PR that describes the current str(repr) behaviour, and
  • A follow-up PR that fixes the wire format at the next breaking-change window.

That said, I'd personally lean toward landing them together. From a user's perspective, the current str(repr) payload feels broken — receivers can't parse it with JSON.parse, and documenting it as the spec would lock that in. Fixing it alongside the docs seems like the cleaner outcome for everyone.

But either way is fine with me — just let me know which one you prefer and I'll do the work. Thanks!

@syou6162
syou6162 force-pushed the doc/day_summary_webhook branch from 9b14340 to 0f91ffc Compare May 24, 2026 20:58
@syou6162

Copy link
Copy Markdown
Collaborator Author

Friendly bump on this — I just had to merge main in once to clear out a conflict (the run_blocking / postprocess_executor rename in utils/executors.py overlapped with this PR's webhooks.py and notifications.py changes), and as the surrounding code keeps moving it gets a bit costlier to keep this rebased clean.

No urgency on the breaking-change decision itself — happy to split into a docs-only PR + a separate JSON-object fix at a later breaking-change window if that's easier for you. I'd just like to avoid the patch surface drifting further before it lands.

cc @beastoin @mdmohsin7 — would either of you have a few minutes to take a look?

@beastoin

Copy link
Copy Markdown
Collaborator

@syou6162 my insight:

The date format should be updated to make it consistent. The summary data should be preserved as is (as a string). We do not need to introduce breaking changes for this, but please update the document, including the pitfall section.

You can also add a new field that returns the JSON object for the summary (which should be done in a breaking change PR), and then use that field from now on (update docs too ofc)

Rebase your PR to ensure it is clean and does not include other changes.

syou6162 added 2 commits May 25, 2026 13:56
`day_summary_webhook` was the only place in `backend/` calling
`datetime.now().isoformat()`, which produces a naive timestamp with no
timezone suffix (`"2024-01-15T22:00:00.123456"`). Receivers had no way
to know whether that was UTC, server-local, or something else.

Switch to `datetime.now(timezone.utc).isoformat()`, matching the
convention used everywhere else in the codebase (`utils/social.py`,
`utils/chat.py`, `utils/agent.py`, `routers/users.py`, etc.). The
webhook now sends `"2024-01-15T22:00:00.123456+00:00"` so receivers
can parse it unambiguously with `datetime.fromisoformat`, `Date.parse`,
etc.

The `summary` field shape itself is intentionally left alone in this
PR — see the accompanying Integrations docs change for the current
wire format and the planned JSON-object follow-up.
The Day Summary webhook (`WebhookType.day_summary`) ships in the app
(Developer Mode → "Day Summary Webhook") and in the backend (cron at
`utils/other/notifications.py` calling `day_summary_webhook`), but the
public Integrations page never described it — developers seeing the
toggle in the app had no way to find the payload spec, the trigger
schedule, or the cases where no webhook is sent.

This adds a "Day Summary" H2 section covering:

- Endpoint shape (`POST /your-endpoint?uid=user123`) and a JSON payload
  example reflecting what the backend actually sends today
- A `<Warning>` block that's blunt about the current wire format: the
  `summary` field is a Python `repr` string (single-quoted, not JSON)
  rather than a JSON object, so receivers cannot use `JSON.parse` and
  currently need a Python-specific parser such as `ast.literal_eval`
  to extract structured fields. A follow-up PR will add a dedicated
  JSON-object field for the summary so receivers don't have to deal
  with this; the warning sets that expectation
- A schema snapshot of the underlying summary `dict` (the same shape
  the future JSON-object field will use) so receivers can plan against
  it
- Delivery conditions where the webhook does *not* fire (no
  conversations, all conversations locked / no transcribed speech, no
  FCM token registered, atomic per-`(uid, date)` Redis lock already
  held)
- Timezone requirement scoped to the scheduled cron path (the manual
  "Generate Summary" trigger falls back to UTC day boundaries)
- A `<Note>` explaining that the in-app "Generate Summary" action
  regenerates the summary but does not currently POST to the developer
  webhook, so the recommended way to validate a receiver is still to
  wait for the next scheduled cron tick

Page-level wiring is brought in line with the new section:

- The intro CardGroup is recomposed to `cols={2}` 2×2 so the four
  trigger types fit cleanly, and a Day Summary card is added
- The mermaid diagram gets a Day Summary source node and edge
- "Choose Your Trigger Type" and "Set Webhook URL" steps now cover
  all four trigger types (also fills the previous Audio Bytes gap)
- The page frontmatter description now mentions daily summaries

No backend behaviour is changed by this commit — `summary` remains a
Python `repr` string on the wire. Aligning the payload to a real JSON
object is deferred to a follow-up breaking-change PR per maintainer
direction.
@syou6162
syou6162 force-pushed the doc/day_summary_webhook branch from 0f91ffc to 1a363da Compare May 25, 2026 04:57
@syou6162 syou6162 changed the title docs: document Day Summary webhook and serialize summary as JSON object docs: document the Day Summary webhook and standardize created_at on UTC May 25, 2026
@syou6162

Copy link
Copy Markdown
Collaborator Author

Thanks for the review @beastoin — fully agree with the direction. I've reworked the PR along the lines you suggested:

What changed

  • Reverted the summary wire-format flip. day_summary_webhook once again sends summary as the str(...) Python repr it does on main. No breaking change to the payload shape in this PR.
  • Kept the created_at consistency fix. That's the only "date format" change here — the top-level webhook timestamp now uses datetime.now(timezone.utc).isoformat() (i.e. …+00:00) to match the rest of backend/. If you meant a different field by "date format", happy to drop this hunk and ship docs-only.
  • Documented the current behaviour honestly, including the pitfall section. New <Warning> block in Integrations.mdx calls out that summary is a Python repr string, not JSON; receivers cannot use JSON.parse and currently need something Python-specific like ast.literal_eval. The accompanying schema snapshot describes the underlying summary object so receivers can plan against the shape.
  • Rebased onto the latest main (git reset --hard upstream/main + replay) so the PR is now 2 clean commits and the diff is just the two files I actually touched — no incidental changes pulled in by earlier merge attempts.

Planned follow-up (separate PR)

Per your suggestion, I'll open a separate breaking-change PR that adds a new field returning the summary as a real JSON object (likely summary_json or similar — naming open to your preference), updates the docs to point receivers at the new field, and starts the deprecation clock on the legacy summary string. Happy to file a tracking issue first if you'd like to lock the field name / deprecation policy before any code lands.

Could you take another look when you have a moment? Thanks again for the steer.

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backend+docs (Day Summary webhook docs) — approve only

@syou6162
syou6162 merged commit 227b909 into BasedHardware:main May 30, 2026
1 check passed
@syou6162
syou6162 deleted the doc/day_summary_webhook branch May 30, 2026 14:45
syou6162 added a commit that referenced this pull request May 31, 2026
Follow-up to #7110, which documented the Day Summary webhook but left
the `summary` wire format intact (a Python `repr` string that JSON
parsers can't read). Per @beastoin's review there, this introduces a
new JSON-object field — `summary_json` — alongside the legacy `summary`
string so receivers can migrate to a properly-parseable payload without
breaking anyone still consuming the old format.

Changes:

- `day_summary_webhook` gains an optional `summary_json: Optional[dict]`
  parameter and includes it in every outgoing payload. When the caller
  doesn't supply one, the field is sent as `null`, which keeps the JSON
  shape stable for receivers regardless of source.
- The only in-tree caller (`_send_summary_notification` in
  `utils/other/notifications.py`) now passes the raw `summary_data`
  dict as `summary_json`, while continuing to wrap it in `str(...)` for
  the legacy `summary` field. Both fields therefore carry the same
  payload — one as a JSON object, one as the historical Python repr.
- The legacy `summary` string is intentionally preserved; deprecation
  will be handled in a separate PR once receivers have had time to
  migrate.
syou6162 added a commit that referenced this pull request May 31, 2026
Adds two test classes for the new `summary_json` field introduced in
the previous commit (follow-up to #7110):

- `TestDaySummaryWebhookJsonField` runs `day_summary_webhook` end to
  end with `get_webhook_client` mocked and asserts on the JSON body
  going to httpx:
    * when a dict is supplied, `summary_json` is sent as a dict, the
      legacy `summary` string is preserved verbatim, and `created_at`
      keeps the `+00:00` UTC offset shipped in #7110
    * when no dict is supplied, `summary_json` is sent as `null` so
      receivers see a stable JSON shape regardless of source
- `TestSendSummaryNotificationWiresSummaryJson` is a static guard
  matching the in-tree pattern (grep the source instead of importing
  the heavy `notifications.py`) that pins the call site to
  `day_summary_webhook(uid, str(summary_data), summary_data)` so a
  regression there would surface immediately.
syou6162 added a commit that referenced this pull request May 31, 2026
Follow-up to #7110, which landed a `<Warning>` block calling out that
the Day Summary webhook's `summary` field is a Python `repr` string
rather than JSON. With the new `summary_json` payload field shipped in
this PR, the docs can now point receivers at a properly-parseable
alternative instead of just describing the pitfall.

Changes in `Integrations.mdx`:

- The Webhook Payload example now shows both fields side by side, with
  `summary_json` as a real nested JSON object and `summary` truncated
  to the legacy `repr` string. This makes the wire shape obvious at a
  glance.
- The field reference table flags `summary_json` as **Recommended** and
  marks `summary` as **Legacy**, replacing the previous single row that
  pointed at the warning.
- The `<Warning>` becomes a `<Note>` that explicitly recommends
  migrating to `summary_json`, explains why the legacy `summary` field
  still exists (backward compatibility), and signals that deprecation
  will follow once receivers have had time to migrate. Receivers no
  longer need `ast.literal_eval` for the new path.
- The clarification about the two `created_at` timestamps is updated to
  reference `summary_json` as the canonical inner-timestamp source,
  with the legacy `summary` string noted as carrying the same value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants