Back to News
OmniCon 2.0.20 — Payment hardening, daily-driver polish

OmniCon 2.0.20 — Payment hardening, daily-driver polish

April 23, 2026

Thirteen merges since 2.0.7. A lot of the work was under the hood on billing — promo codes, idempotent webhooks, plan-quota enforcement — but there's also a pile of small-but-nice daily-driver polish: remembered tag filters, drag-and-drop hero images, searchable-state chips, org-wide channel visibility.


2.0.20 is thirteen merges stacked on 2.0.7. A lot of the effort was under the hood on billing — promo codes at checkout, idempotent webhooks, proper plan-quota enforcement — but there's also a pile of small-but-nice daily-driver polish that shows up the moment you log in.

Daily-driver polish

  • Drag-and-drop main image. The article create/edit form no longer needs the four-step detour through the editor's image dialog. Drop an image on the new drop zone, see the preview, done. Change and Remove buttons sit on the preview for one-click swaps. If you already have a URL, Paste URL instead is still a collapsed option.
  • Searchable state is visible at a glance. Channel article lists now show a green Searchable chip or a muted Not searchable chip next to each article's tags — so you can scan a channel and immediately see which posts are excluded from public search, instead of squinting for an icon that might or might not be there.
  • Tag filter on the channel dashboard is sticky. Click a tag to filter your channel list, leave, come back — you land on the same filter. Scoped per-organization and remembered for 180 days. Clicking All clears it explicitly.
  • Hero buttons split into Help and API Docs. The homepage hero now has a dedicated Help button that lands on the Welcome index, and a separate API Docs button that goes straight to the Swagger UI at api.omnicon.cloud. No more conflating the two.

Teamwork

  • Org members see every channel. Previously, joining an organization didn't automatically let you see the channels inside it — an admin also had to add you to each channel individually. Now if you're in the organization, every non-archived channel in it is visible. The per-channel membership table is still used for Owner/Editor/Reviewer roles, but it's no longer a gate on visibility.
  • MCP can do a lot more. The update_article MCP tool used to only accept title, body, description, tags, permalink, and main-image URL. It now covers every field the portal create/edit form exposes: author, publish window, folders, culture, searchable, archived, isArticle, and the HTML/Markdown content flags — with timestamp validation and cross-field checks. Agents can now actually rewrite an article instead of drafting one and handing it off.

Billing, end to end

Most of the release was here. Payment was stable but had a list of latent problems that would bite the moment we started running promos or hitting retry storms.

  • Promo codes. Stripe's hosted Checkout now shows the Add promotion code input. Coupons are managed in the Stripe Dashboard — create a coupon, attach a code, it works immediately. Discounts flow back to our invoice mirror automatically.
  • Webhooks are idempotent. A replayed Stripe webhook (which happens any time a delivery times out) used to double-apply plan changes. We now write a row per processed event id and skip any replay. No more duplicate invoice entries or flag flapping.
  • Callback URLs are hardened. Stripe success/cancel and billing-portal return URLs are now pinned to AppConfig.PublicBaseUrl instead of being built from the incoming Host header. Removes a forged-host redirect hazard.
  • Storage quota is real. Every plan has a StorageGb limit, but nothing was reading it — a free-tier org could upload the same as premium. Uploads now sum the org's StorageItems row history and reject anything that would cross the plan's limit, with a helpful error naming the plan tier and current usage.
  • Subscription status is typo-proof. A new SubscriptionStatuses constants class replaces every magic string ("active", "canceling", "past_due", …). One typo used to be enough to silently break a gate.
  • Customer creation is race-proof. CustomerService.CreateAsync now carries an idempotency key pinned to the org id, so two concurrent first-subscribes can't produce duplicate Stripe customers — even across multiple app instances.
  • Webhook signature failures are visible. Bad signatures used to return a silent 400. We now log a structured warning with the remote IP so misconfigured secrets and probing attempts both show up in App Insights.
  • Proration policy is documented. create_prorations was already set on plan changes; a comment now spells out why so a future reader doesn't remove it thinking it's a leftover default.

For background on the whole payment surface and how promo codes work under the hood, the Welcome page is still the right starting point. If you're an engineer on the team, the internal wiki has two new articles — Payment, billing, and the promo code plan and Coupons and promo codes — with the operator walkthrough.

release billing polish mcp