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
2 changes: 1 addition & 1 deletion .actor/actor.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "webhook-debugger-logger",
"title": "Webhook Debugger, Logger & API Mocking Suite",
"description": "Enterprise-grade tool to test, debug, and mock webhooks. Features real-time SSE streaming, request replay, HTTP forwarding, and JSON schema validation. Perfect for Stripe, GitHub, and Shopify integrations.",
"version": "3.0.3",
"version": "3.0.4",
"output": "./output_schema.json",
"input": "./input_schema.json",
"webServerSchema": "./web_server_schema.json",
Expand Down
159 changes: 129 additions & 30 deletions .actor/dataset_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,31 @@
}
},
"body": {
"type": "string",
"oneOf": [
{
"type": "string"
},
{
"type": "object"
},
{
"type": "array"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Body",
"description": "The raw or parsed request body",
"example": "{\"status\": \"success\"}"
"description": "The raw request body as text, or a parsed/offloaded JSON payload",
"example": {
"status": "success"
}
},
Comment thread
ar27111994 marked this conversation as resolved.
"contentType": {
"type": "string",
Expand Down Expand Up @@ -106,16 +127,39 @@
"example": 200
},
"responseBody": {
"type": "string",
"oneOf": [
{
"type": "string"
},
{
"type": "object"
},
{
"type": "array"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Response Body",
"description": "Body returned to the webhook sender",
"example": "{\"received\": true}"
"description": "Response body returned to the webhook sender as text or JSON",
"example": {
"received": true
}
},
Comment thread
ar27111994 marked this conversation as resolved.
"responseHeaders": {
"type": "object",
"title": "Response Headers",
"description": "Headers returned to the webhook sender",
"example": { "content-type": "application/json" }
"example": {
"content-type": "application/json"
}
},
"signatureProvider": {
"type": "string",
Expand Down Expand Up @@ -153,17 +197,41 @@
"display": {
"component": "table",
"properties": {
"timestamp": { "label": "Time", "format": "date" },
"requestId": { "label": "Req ID" },
"signatureValid": { "label": "Sig", "format": "boolean" },
"webhookId": { "label": "Webhook ID" },
"method": { "label": "Method" },
"statusCode": { "label": "Status" },
"contentType": { "label": "Content-Type" },
"size": { "label": "Size (Bytes)" },
"processingTime": { "label": "Latency (ms)" },
"remoteIp": { "label": "Source IP" },
"signatureProvider": { "label": "Provider" }
"timestamp": {
"label": "Time",
"format": "date"
},
"requestId": {
"label": "Req ID"
},
"signatureValid": {
"label": "Sig",
"format": "boolean"
},
"webhookId": {
"label": "Webhook ID"
},
"method": {
"label": "Method"
},
"statusCode": {
"label": "Status"
},
"contentType": {
"label": "Content-Type"
},
"size": {
"label": "Size (Bytes)"
},
"processingTime": {
"label": "Latency (ms)"
},
"remoteIp": {
"label": "Source IP"
},
"signatureProvider": {
"label": "Provider"
}
}
}
},
Expand All @@ -189,18 +257,49 @@
"display": {
"component": "table",
"properties": {
"timestamp": { "label": "Time", "format": "date" },
"requestId": { "label": "Req ID" },
"signatureValid": { "label": "Sig", "format": "boolean" },
"method": { "label": "Method" },
"statusCode": { "label": "Status" },
"query": { "label": "Query", "format": "object" },
"headers": { "label": "Headers", "format": "object" },
"body": { "label": "Body", "format": "text" },
"responseBody": { "label": "Res Body", "format": "text" },
"responseHeaders": { "label": "Res Headers", "format": "object" },
"signatureProvider": { "label": "Sig Provider" },
"signatureError": { "label": "Sig Error" }
"timestamp": {
"label": "Time",
"format": "date"
},
"requestId": {
"label": "Req ID"
},
"signatureValid": {
"label": "Sig",
"format": "boolean"
},
"method": {
"label": "Method"
},
"statusCode": {
"label": "Status"
},
"query": {
"label": "Query",
"format": "object"
},
"headers": {
"label": "Headers",
"format": "object"
},
"body": {
"label": "Body",
"format": "text"
},
"responseBody": {
"label": "Res Body",
"format": "text"
},
"responseHeaders": {
"label": "Res Headers",
"format": "object"
},
"signatureProvider": {
"label": "Sig Provider"
},
"signatureError": {
"label": "Sig Error"
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions .actor/web_server_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "Webhook Debugger & Logger API",
"description": "OpenAPI description for the Webhook Debugger & Logger Actor web server. Authentication is configuration-driven: when authKey is configured, management routes require either a bearer token or the key query parameter; when authKey is unset, those routes remain accessible without credentials.",
"version": "3.0.3"
"version": "3.0.4"
},
"servers": [
{
Expand Down Expand Up @@ -80,7 +80,7 @@
"schema": {
"type": "string"
},
"example": "Webhook Debugger & Logger (v3.0.3)\nActive Webhooks: 1\nSignature Verification: STRIPE"
"example": "Webhook Debugger & Logger (v3.0.4)\nActive Webhooks: 1\nSignature Verification: STRIPE"
}
}
},
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.4] - 2026-04-18

### Fixed (3.0.4)

- **Apify**: Relax the dataset storage schema for `body` and `responseBody` so webhook events can be stored when request or response payloads are captured as any JSON-compatible values instead of only strings and objects.
- **Tests**: Add a regression test that keeps the Actor dataset schema aligned with the webhook payload shapes persisted by the runtime.

## [3.0.3] - 2026-04-18

### Fixed (3.0.3)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webhook-debugger-logger",
"version": "3.0.3",
"version": "3.0.4",
"type": "module",
"description": "Generate temporary webhook URLs and log all incoming requests with full details.",
"main": "src/main.js",
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/actor/input_schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { getInputSchemaSecretFieldKeys } from "@apify/input_secrets";
import { createRequire } from "node:module";

const require = createRequire(import.meta.url);
const Ajv = require("ajv").default;
Comment thread
ar27111994 marked this conversation as resolved.
const inputSchema = require("../../../.actor/input_schema.json");
const datasetSchema = require("../../../.actor/dataset_schema.json");

/**
* @param {unknown} value
Expand Down Expand Up @@ -130,3 +132,57 @@ describe("Apify input schema", () => {
expect(missingDescriptions).toEqual([]);
});
});

describe("Apify dataset schema", () => {
it("allows webhook body fields to be stored in the runtime-supported JSON shapes", () => {
const ajv = new Ajv({ strict: false, validateFormats: false });
const validate = ajv.compile(datasetSchema.fields);

expect(
validate({
body: { status: "success" },
responseBody: { received: true },
}),
).toBe(true);
expect(validate.errors).toBeNull();

expect(
validate({
body: '{"status":"success"}',
responseBody: '{"received":true}',
}),
).toBe(true);
expect(validate.errors).toBeNull();

expect(
validate({
body: ["first", "second"],
responseBody: null,
}),
).toBe(true);
expect(validate.errors).toBeNull();

expect(
validate({
body: 123,
responseBody: true,
}),
).toBe(true);
expect(validate.errors).toBeNull();

expect(
validate({
body: false,
responseBody: ["one", "two", "three"],
}),
).toBe(true);
expect(validate.errors).toBeNull();

expect(
validate({
body: () => "not-json",
}),
).toBe(false);
expect(validate.errors).not.toBeNull();
});
});
Loading