Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ Convoy provides several key features:
process events, convoy disables the endpoint and sends out a notification.
Email, Slack, and Microsoft Teams are supported.

- **Search:** Find events by id, type, source, or idempotency key, or paste JSON
to search the payload. Results stay in the date range you already have open.
- **Search:** Choose event metadata, JSON payload, idempotency key, or
idempotency value from the search icon menu. Results stay in the selected date range.
See [event search](docs/event-search.md) for matching rules and API parameters.

- **Filters & Transforms:** Subscriptions can filter on body, headers, query,
and path, and run a JS transform before delivery. Events that do not match
Expand Down
8 changes: 8 additions & 0 deletions api/models/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ type QueryListEvent struct {
// A JSON object uses payload containment, same as body. Text plus JSON ANDs both.
Query string `json:"query"`

// Set to text to search metadata literally, without interpreting JSON in query.
QueryMode string `json:"queryMode"`

// URL-encoded JSON object matched against the event payload.
// Combined with query as AND when both are set.
Body string `json:"body"`
Expand Down Expand Up @@ -175,6 +178,10 @@ type QueryListEventResponse struct {
}

func (qs *QueryListEvent) Transform(r *http.Request) (*QueryListEventResponse, error) {
queryMode := r.URL.Query().Get("queryMode")
if queryMode != "" && queryMode != "text" {
return nil, errors.New("queryMode must be text or omitted")
}
searchParams, err := getSearchParams(r)
if err != nil {
return nil, err
Expand All @@ -195,6 +202,7 @@ func (qs *QueryListEvent) Transform(r *http.Request) (*QueryListEventResponse, e
Filter: &datastore.Filter{
OwnerID: r.URL.Query().Get("ownerId"),
Query: r.URL.Query().Get("query"),
QueryMode: queryMode,
Body: body,
IdempotencyKey: idempotencyKey,
BrokerMessageId: r.URL.Query().Get("brokerMessageId"),
Expand Down
13 changes: 13 additions & 0 deletions api/models/event_delivery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,16 @@ func TestQueryListEventTransform_RejectsValueWithKey(t *testing.T) {
require.Equal(t, "abc", got.IdempotencyValue)
require.Equal(t, []string{"src_1"}, got.Filter.SourceIDs)
}

func TestQueryListEventTransform_QueryMode(t *testing.T) {
var q *QueryListEvent
r := httptest.NewRequest("GET", "/events?query=invoice.paid&queryMode=text", nil)
got, err := q.Transform(r)
require.NoError(t, err)
require.Equal(t, "text", got.Filter.QueryMode)
require.Equal(t, "invoice.paid", got.Filter.Query)

r = httptest.NewRequest("GET", "/events?queryMode=unknown", nil)
_, err = q.Transform(r)
require.ErrorContains(t, err, "queryMode must be text or omitted")
}
1 change: 1 addition & 0 deletions datastore/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

type Filter struct {
Query string
QueryMode string
Body json.RawMessage
OwnerID string
UserID string
Expand Down
20 changes: 19 additions & 1 deletion docs/docs.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package docs Code generated by swaggo/swag at 2026-10-16 22:13:32.055407 +0100 WAT m=+1.217868251. DO NOT EDIT
// Package docs Code generated by swaggo/swag at 2026-10-20 12:12:54.5608 +0100 WAT m=+2.285550043. DO NOT EDIT
package docs

import "github.com/swaggo/swag"
Expand Down Expand Up @@ -3567,6 +3567,12 @@ const docTemplate = `{
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down Expand Up @@ -3932,6 +3938,12 @@ const docTemplate = `{
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down Expand Up @@ -4248,6 +4260,12 @@ const docTemplate = `{
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down
48 changes: 48 additions & 0 deletions docs/event-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Event search

The Events log has one search field. Use the dropdown on its search icon to
choose a search type:

The control at the start of the field shows a magnifier for metadata, braces
for payload, a key for the stored idempotency key, or a hash for the raw
idempotency value. Hover or focus it for the mode name, or open the dropdown
to switch modes. Each mode has its own placeholder and help text.

- **Event metadata:** Find an event ID prefix or a match in its event type,
source name, or idempotency key. Input is treated as text, including braces.
- **JSON payload:** Enter fields such as `status: "paid"` or
`data: {status: "paid"}`. A complete JSON object works too. Outer braces and
quotes around simple field names are optional; string values need double
quotes. All supplied fields must match the payload at the same nesting level.
The dashboard normalizes the input to JSON before sending it. Invalid input shows a
validation message and keeps the last successfully applied results.
- **Idempotency key:** Enter the exact stored key shown in event details.
This is a separate lookup from the raw value received from a provider.
- **Idempotency value:** Enter the exact raw value received at a source's
idempotency key location. Use the source filter if several sources qualify.
This value can differ from the stored idempotency key shown in event details.

Switching search type clears the previous search. Date and source filters stay
selected. The selected date window applies to every search type.

Metadata and JSON payload search require the Event Search entitlement.

## API

For `GET /events`, use these query parameters:

- `query=invoice.paid&queryMode=text` searches metadata without interpreting
JSON in the query.
- `body={"status":"paid"}` searches payload containment. URL-encode the JSON.
- `idempotencyValue=provider-delivery-123` looks up the raw provider value.
- `idempotencyKey=stored-key` looks up the stored key exactly.

Existing requests that omit `queryMode` retain automatic JSON detection in
`query`. An explicit `query` and `body` can still be combined as an AND filter.
Other `queryMode` values are rejected. Provider lookup cannot be combined with
`idempotencyKey` and is unavailable to portal links.

The event payload viewer preserves valid JSON formatting. Its Copy button
copies the complete formatted payload even while the viewer is collapsed.
Hovering over the idempotency key reveals its Copy control and full-value
tooltip; the control is also available on keyboard focus.
18 changes: 18 additions & 0 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4014,6 +4014,12 @@
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down Expand Up @@ -4419,6 +4425,12 @@
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down Expand Up @@ -4765,6 +4777,12 @@
"name": "query",
"in": "query"
},
{
"type": "string",
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"name": "queryMode",
"in": "query"
},
{
"type": "string",
"example": "ASC | DESC",
Expand Down
12 changes: 12 additions & 0 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5081,6 +5081,10 @@ paths:
in: query
name: query
type: string
- description: Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
type: string
- description: Sort order, values are `ASC` or `DESC`, defaults to `DESC`
example: ASC | DESC
in: query
Expand Down Expand Up @@ -5450,6 +5454,10 @@ paths:
in: query
name: query
type: string
- description: Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
type: string
- description: Sort order, values are `ASC` or `DESC`, defaults to `DESC`
example: ASC | DESC
in: query
Expand Down Expand Up @@ -5657,6 +5665,10 @@ paths:
in: query
name: query
type: string
- description: Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
type: string
- description: Sort order, values are `ASC` or `DESC`, defaults to `DESC`
example: ASC | DESC
in: query
Expand Down
24 changes: 24 additions & 0 deletions docs/v3/openapi3.json
Original file line number Diff line number Diff line change
Expand Up @@ -4502,6 +4502,14 @@
"type": "string"
}
},
{
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"in": "query",
"name": "queryMode",
"schema": {
"type": "string"
}
},
{
"description": "Sort order, values are `ASC` or `DESC`, defaults to `DESC`",
"example": "ASC | DESC",
Expand Down Expand Up @@ -4967,6 +4975,14 @@
"type": "string"
}
},
{
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"in": "query",
"name": "queryMode",
"schema": {
"type": "string"
}
},
{
"description": "Sort order, values are `ASC` or `DESC`, defaults to `DESC`",
"example": "ASC | DESC",
Expand Down Expand Up @@ -5365,6 +5381,14 @@
"type": "string"
}
},
{
"description": "Set to text to search metadata literally, without interpreting JSON in query.",
"in": "query",
"name": "queryMode",
"schema": {
"type": "string"
}
},
{
"description": "Sort order, values are `ASC` or `DESC`, defaults to `DESC`",
"example": "ASC | DESC",
Expand Down
18 changes: 18 additions & 0 deletions docs/v3/openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2436,6 +2436,12 @@ paths:
name: query
schema:
type: string
- description: >-
Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
schema:
type: string
- description: 'Sort order, values are `ASC` or `DESC`, defaults to `DESC`'
example: ASC | DESC
in: query
Expand Down Expand Up @@ -2702,6 +2708,12 @@ paths:
name: query
schema:
type: string
- description: >-
Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
schema:
type: string
- description: 'Sort order, values are `ASC` or `DESC`, defaults to `DESC`'
example: ASC | DESC
in: query
Expand Down Expand Up @@ -2936,6 +2948,12 @@ paths:
name: query
schema:
type: string
- description: >-
Set to text to search metadata literally, without interpreting JSON in query.
in: query
name: queryMode
schema:
type: string
- description: 'Sort order, values are `ASC` or `DESC`, defaults to `DESC`'
example: ASC | DESC
in: query
Expand Down
Loading
Loading