Skip to content

feat(webhooks): add delivery status, event class, and event type filters#4931

Open
XyneSpaces wants to merge 1 commit into
mainfrom
feat/3036-webhook-filters
Open

feat(webhooks): add delivery status, event class, and event type filters#4931
XyneSpaces wants to merge 1 commit into
mainfrom
feat/3036-webhook-filters

Conversation

@XyneSpaces

Copy link
Copy Markdown
Collaborator

Type of Change

  • New Feature

Description

Adds three new filters to the webhook events list that were already supported by the backend API but missing in the control-center UI:

  1. Delivery Status (single-select): Delivered / Failed
  2. Event Class (multi-select): Payments, Refunds, Disputes, Mandates, Payouts, Subscriptions
  3. Event Type (multi-select): 27 event types (PaymentSucceeded, RefundFailed, etc.)

Changes Made

API Layer (src/APIUtils/)

  • Added WEBHOOK_EVENTS_FILTERS entity to APIUtilsTypes.res
  • Mapped to events/profile/filter GET endpoint in APIUtils.res

Webhook Events Screen (src/screens/Developer/Webhooks/)

  • WebhooksUtils.res: Added filter field definitions and options
  • Webhooks.res: Wired filters to POST payload sent to /events/profile/list

Filter Value Mapping

  • Delivery Status: Maps "delivered" → is_delivered: true, "failed" → is_delivered: false
  • Event Classes/Types: Sent as arrays to backend (event_classes, event_types)

Backend Support

Backend already supports these filters via EventListConstraints struct in crates/api_models/src/webhook_events.rs:

pub struct EventListConstraints {
    pub event_classes: Option<HashSet<EventClass>>,
    pub event_types: Option<HashSet<EventType>>,
    pub is_delivered: Option<bool>,
    // ... other existing fields
}

Testing

  • ✅ ReScript build: 2,746 modules compiled successfully
  • Manual verification: Filters appear in UI and correctly modify API payload

Closes

Closes #3036

Motivation and Context

Merchants need to filter webhook events by delivery status, event class, and event type to quickly find relevant events when debugging webhook delivery issues.

How did you test it?

  1. ReScript build passed (npm run re:build)
  2. Verified filter UI renders with correct options
  3. Inspected network payload to confirm event_classes, event_types, and is_delivered fields are sent

Where to test it?

  • SANDBOX
  • INTEG
  • PROD

Backend Dependency

  • No (backend already supports these filters)

Feature Flag

  • No

… event type

Adds event_classes, event_types, and is_delivered filters to webhook events list. Wires filters to POST payload matching backend EventListConstraints schema. Adds WEBHOOK_EVENTS_FILTERS entity for fetching filter options. Maps delivery status UI values to backend boolean. Preserves existing date range and search by ID functionality.

Refs: #3036
@XyneSpaces XyneSpaces requested a review from a team as a code owner June 5, 2026 14:05
@semanticdiff-com

Copy link
Copy Markdown

Review changes with  SemanticDiff

@XyneSpaces XyneSpaces left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review Summary

Classification: Feature Addition — Webhook Event Filters
Risk Level: Low-Medium
Files Changed: 4 files


Verification Against Control Center Checklist

Tier 1 — High Signal:

Naming Consistency — Filter field names (event_classes, event_types, delivery_status) match backend API expectations
Existing Utilities — Uses useGetMethod, InputFields.multiSelectInput, InputFields.singleSelectInput appropriately
Error HandlingfetchFilterOptions has try-catch with silent failure (acceptable for filter options loading)


Findings

⚠️ Silent catch in fetchFilterOptions

catch {
| _ => ()
}

The catch block silently swallows all errors when fetching filter options. While non-critical (filters falling back to empty is acceptable), consider logging the error for debugging purposes.

Fix: Add error logging:

catch {
| err =>
  logger::error("Failed to fetch webhook filter options", ~err)
  ()
}

💡 mapDeliveryStatusToBackend could use variant instead of string

The current string-based mapping works, but using a ReScript variant would provide compile-time safety:

type deliveryStatus = Delivered | Failed | Unknown

let mapDeliveryStatusToBackend = (status: deliveryStatus) =>
  switch status {
  | Delivered => Some(true)
  | Failed => Some(false)
  | Unknown => None
  }

Not blocking — current implementation is functional.


Positive Notes

  • Clean separation between UI delivery status strings and backend boolean
  • Proper use of existing form/input components
  • Feature flag devClickhouseAggregate properly respected
  • Good alignment with existing webhook screen patterns

Verdict:Approve — Clean feature addition with proper filter integration. Minor suggestion on error logging.

@XyneSpaces

Copy link
Copy Markdown
Collaborator Author

Automated Review: Webhook Filters Feature

Classification: UI Feature — Add delivery status, event class, and event type filters
Risk Level: Low
Scope: src/APIUtils/, src/screens/Developer/Webhooks/


Verification Summary

Tier 1 — High Signal:

  • ✅ Naming consistency — WEBHOOK_EVENTS_FILTERS follows existing entity naming pattern
  • ✅ Reuse existing utilities — Uses LogicUtils.getArrayFromDict, getDictFromJsonObject, getString
  • ✅ Feature flag gating — N/A (backend already supports, UI enabling feature)

Findings

⚠️ Empty catch block silently swallows fetch errors

catch {
| _ => ()
}

The fetchFilterOptions catch block silently discards all errors. If the filter endpoint fails (network error, 5xx, etc.), the UI shows empty dropdowns with no indication of failure.

Fix: Add error handling to inform users:

catch {
| _ => {
    // Log error and show toast notification
    logger->error("Failed to load webhook filter options")
    showToast(~message="Failed to load filters", ~toastType=ToastError)
  }
}

🔍 Whitespace-only formatting change detected

-            }
+              }

Line 1199 in APIUtils.res has a whitespace-only indentation change. This is scope creep and should be removed.

Fix: Revert the indentation-only change on the closing brace.


💡 Consider caching filter options

The fetchFilterOptions runs on every component mount. Since event classes and event types are relatively stable (defined by the backend API), consider caching these in a Recoil atom to reduce API calls.

Fix: Store fetched options in a global atom keyed by profile_id to persist across navigation.


🔍 Delivery status options hardcoded

The delivery status options are hardcoded as [{"label": "Delivered", "value": "delivered"}, {"label": "Failed", "value": "failed"}]. If the backend adds new statuses (e.g., "Pending"), the UI won't reflect them.

Fix: Consider fetching these from the backend along with event classes/types, or document the assumption that these are the only two valid states.


💡 Missing empty state handling for empty arrays

if eventClasses->Array.length > 0 {
  payload->Dict.set("event_classes", eventClasses->JSON.Encode.array)
}

This correctly omits empty arrays from the payload, but there's no visual indication to users that the dropdown is empty due to no data vs. still loading.

Fix: Add a loading state to filterOptions so the UI shows a spinner while fetching, and an empty message if no options are available.


Positive Notes

  • ✅ Clean reuse of existing ReScript patterns (FormRenderer.makeFieldInfo, InputFields.multiSelectInput)
  • ✅ Proper type definitions for filterOptions type
  • ✅ Delivery status correctly maps to boolean is_delivered for backend compatibility
  • ✅ Filters correctly omit empty values from payload (avoids sending empty arrays)
  • ✅ Multi-select inputs have proper UX features (chips, searchable, clear all)

Pre-existing Issues

None identified in changed files.


Verdict: ⚠️ Request Changes — Address the silent error swallowing and remove whitespace-only change before merge.

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.

webhook additional filters integration

1 participant