Webhook Event Normalizer
integration.webhook_event_normalize
Overview
You send headers, bodyJson, receivedAt and profile GENERIC_CLOUDEVENTS, STRIPE_LIKE or GITHUB_LIKE. Header names are case-folded. Authorization and Cookie are discarded and never returned. Missing id or type become diagnostics with valid false. This does not verify signatures, fetch provider schemas or deserialize non-JSON bodies.
Each provider hides the event id in a different header; agents should not re-learn that map.
Reliability
Authorization and Cookie are discarded. WEBHOOK_INVALID — trigger: a malformed ISO-8601 instant or a non-string header value; retryable: no; next: send ISO-8601 receivedAt and string headers. WEBHOOK_LIMIT_EXCEEDED — trigger: bodyJson larger than 2 MiB; retryable: no; next: shrink the body.
Context footprint
Normalized fields and diagnostics only; sensitive headers never appear. A successful call costs 1 execution credit.
When to use it
When a parsed webhook envelope must become a provider-neutral event record.
When you need to verify a signature or fetch a provider schema.
Scope
Non-goals
- Does not verify signatures
- Does not fetch provider schemas
- Does not deserialize non-JSON bodies
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill is designed to detect, mask, redact, validate, or otherwise process sensitive data.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- Targets sensitive data
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| headers | object | No | Purpose-bound header map; names are case-folded and Authorization or Cookie are discarded. Omit it for no headers. |
| bodyJson | object | Yes | Open map of the parsed JSON body; adapters read a published field map and extra keys pass through. |
| receivedAt | string | Yes | ISO-8601 instant when the envelope was received. |
| profile | string | Yes | GENERIC_CLOUDEVENTS, STRIPE_LIKE or GITHUB_LIKE adapter. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| valid | boolean | Yes | True when event id and type were found. |
| profile | string | Yes | Echoed adapter profile. |
| eventId | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| eventType | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| subject | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| occurredAt | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| receivedAt | string | Yes | Echoed ISO-8601 receive instant. |
| deliveryId | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| dataVersion | string | Yes | Normalized string field; empty when the adapter did not find a value. |
| data | object | Yes | Open map of normalized event data; non-object payloads are wrapped under value. |
| provenance | array | Yes | Bounded array of 1 through 8 provenance tags naming the adapter field map. |
| diagnostics | array | Yes | Bounded array of 0 through 500 closed findings, each with code, severity, message, path, source and details. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"integration.webhook_event_normalize","input":{"profile":"GENERIC_CLOUDEVENTS","receivedAt":"2026-08-27T00:00:00Z","bodyJson":{"id":"1","type":"created"}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("integration.webhook_event_normalize", {"receivedAt":"2026-08-27T00:00:01Z","headers":{"ce-id":"evt-1","ce-type":"com.example.created"},"profile":"GENERIC_CLOUDEVENTS","bodyJson":{"data":{"id":"1"}}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| WEBHOOK_INVALID | 400 | No | The request payload failed this skill's input validation. |
| WEBHOOK_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not verify signatures
- Does not fetch provider schemas
- Does not deserialize non-JSON bodies
- Verifying HMAC signatures
- Storing the event for deduplication
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "integration.webhook_event_normalize",
"input": {
"receivedAt": "2026-08-27T00:00:01Z",
"headers": {
"ce-id": "evt-1",
"ce-type": "com.example.created"
},
"profile": "GENERIC_CLOUDEVENTS",
"bodyJson": {
"data": {
"id": "1"
}
}
}
}Examples
CloudEvents id and type are read from headers
{
"receivedAt": "2026-08-27T00:00:01Z",
"headers": {
"ce-id": "evt-1",
"ce-type": "com.example.created"
},
"profile": "GENERIC_CLOUDEVENTS",
"bodyJson": {
"data": {
"id": "1"
}
}
}{
"valid": true,
"eventId": "evt-1",
"deliveryId": "evt-1",
"occurredAt": "",
"eventType": "com.example.created",
"receivedAt": "2026-08-27T00:00:01Z",
"data": {
"id": "1"
},
"subject": "",
"diagnostics": [],
"dataVersion": "",
"profile": "GENERIC_CLOUDEVENTS",
"provenance": [
"cloudevents-id-type"
]
}A Stripe-like body without id is valid false
{
"profile": "STRIPE_LIKE",
"bodyJson": {
"created": 1777766400
},
"receivedAt": "2026-08-27T00:00:01Z"
}{
"valid": false,
"eventId": "",
"deliveryId": "",
"occurredAt": "2026-05-03T00:00:00Z",
"eventType": "",
"receivedAt": "2026-08-27T00:00:01Z",
"data": {
"created": 1777766400
},
"subject": "",
"diagnostics": [
{
"code": "MISSING_EVENT_ID",
"severity": "ERROR",
"message": "The profile could not read an event id.",
"path": "/bodyJson",
"source": "squillify",
"details": {}
},
{
"code": "MISSING_EVENT_TYPE",
"severity": "ERROR",
"message": "The profile could not read an event type.",
"path": "/bodyJson",
"source": "squillify",
"details": {}
}
],
"dataVersion": "",
"profile": "STRIPE_LIKE",
"provenance": [
"stripe-id-type"
]
}Recommended use cases
- Read CloudEvents id and type from headers
- Reject a Stripe-like body that has no id
Avoid for
- Verifying HMAC signatures
- Storing the event for deduplication
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.