Webhook Payloads
Mammoth delivers normalized CDC work as JSON over HTTP. The payload is either one row-level change event or one transaction envelope containing all row changes committed by a PostgreSQL transaction.
These serialized forms are supported v1 contracts. Receivers must tolerate
additive fields and additive columns inside data, identity, changes, and
metadata.
Machine-readable contract roadmap
Mammoth currently documents its canonical webhook event and transaction payloads in this guide. The long-term contract should also be published as versioned JSON Schema artifacts owned by Mammoth:
schemas/webhook-event.v1.schema.json
schemas/webhook-transaction.v1.schema.json
Downstream consumers should then reference or vendor schemas from a pinned Mammoth release instead of independently recreating Mammoth's transport contract. The published schemas must remain aligned with the canonical serializers and Mammoth's 1.x compatibility guarantees.
Row-level event
With delivery.unit: event, Mammoth sends one payload per
CDC::Core::ChangeEvent:
{
"event_id": "evt_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"source": "pgoutput",
"operation": "update",
"namespace": "public",
"entity": "orders",
"identity": {
"id": 4
},
"source_position": "0/1990620",
"transaction_id": 765,
"occurred_at": "2026-07-18T14:34:39Z",
"data": {
"id": 4,
"status": "paid",
"total_cents": 4999
},
"changes": [
{
"name": "status",
"old_value": "pending",
"new_value": "paid"
}
],
"metadata": {
"source": "pgoutput",
"relation_id": 16386,
"pgoutput_event": "Update"
}
}
| Field | Meaning |
|---|---|
event_id |
Stable, opaque idempotency key for this normalized event. |
source |
Source adapter label, normally pgoutput for live PostgreSQL delivery. |
operation |
insert, update, or delete. |
namespace |
PostgreSQL schema name. |
entity |
PostgreSQL table name. |
identity |
Replica-identity columns and values for the affected row, or null when unavailable. |
source_position |
Source-provided commit/WAL position associated with the event, or null. |
transaction_id |
Source transaction identifier, or null. |
occurred_at |
Source event time when available; otherwise Mammoth's serialization time. |
data |
New row values for inserts and updates; old row values for deletes. |
changes |
Column differences using the canonical shape below. Always an array for supported payloads. |
metadata |
Additive source-specific context. |
When a destination has an active payload policy, Mammoth may remove or mask
configured columns and adds
metadata.mammoth_payload_policy.fingerprint. The policy applies to data,
identity, and old/new values in changes. See
Payload Policies.
Transaction envelope
With the recommended delivery.unit: transaction, Mammoth sends one
transaction.committed envelope:
{
"event_id": "txn_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"type": "transaction.committed",
"source": "pgoutput",
"transaction_id": 765,
"source_position": "0/1990628",
"commit_lsn": "0/1990628",
"committed_at": "2026-07-18T14:34:39Z",
"event_count": 1,
"events": [
{
"event_id": "evt_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"source": "pgoutput",
"operation": "update",
"namespace": "public",
"entity": "orders",
"identity": {
"id": 4
},
"source_position": "0/1990620",
"transaction_id": 765,
"occurred_at": "2026-07-18T14:34:39Z",
"data": {
"id": 4,
"status": "paid",
"total_cents": 4999
},
"changes": [
{
"name": "status",
"old_value": "pending",
"new_value": "paid"
}
],
"metadata": {
"source": "pgoutput",
"relation_id": 16386,
"pgoutput_event": "Update"
}
}
],
"metadata": {
"source": "pgoutput",
"pgoutput_event": "Commit"
}
}
| Field | Meaning |
|---|---|
event_id |
Stable, opaque idempotency key for the committed transaction envelope. |
type |
Always transaction.committed. |
source |
Source label from the first event, or Mammoth's PostgreSQL default. |
transaction_id |
Source transaction identifier. |
source_position |
Envelope commit position, falling back to the last event position. |
commit_lsn |
Same resolved commit position as source_position. |
committed_at |
Source commit time when available; otherwise Mammoth's serialization time. |
event_count |
Number of entries in events. |
events |
Ordered row-level event payloads in the committed transaction. |
metadata |
Additive transaction/source context. |
Use the envelope event_id to deduplicate transaction-level side effects.
Use a child event's event_id only when applying each row event independently.
Persist the idempotency key atomically with the destination's business
mutation.
Column changes
Each computed entry has this shape:
{
"name": "status",
"old_value": "pending",
"new_value": "paid"
}
Unchanged columns are omitted. The operation determines the meaning:
| Operation | changes behavior |
|---|---|
insert |
One entry per available inserted column, with old_value: null. |
update |
One entry per changed column when complete old and new rows are available. |
delete |
One entry per available deleted column, with new_value: null. |
PostgreSQL normally supplies only replica-identity columns from the old row for
an update. Mammoth does not infer differences from that incomplete comparison,
because doing so would produce false null-to-value changes. In that case,
changes is:
[]
For an update, an empty array can therefore mean either:
- column differences were unavailable because PostgreSQL did not provide complete old and new rows; or
- complete rows were available but no values differed.
Consumers that must distinguish those cases should configure the table with
REPLICA IDENTITY FULL and account for its increased WAL volume and exposure
of previous column values:
ALTER TABLE public.orders REPLICA IDENTITY FULL;
Use REPLICA IDENTITY DEFAULT to opt out when current row state in data is
sufficient:
ALTER TABLE public.orders REPLICA IDENTITY DEFAULT;
Persisted payload replay preserves an existing explicit changes array.
An explicit null metadata value is normalized to an empty array; when no
explicit value exists, Mammoth computes changes from the available row images
using the rules above.
Dead letters produced by a payload-policy destination contain the exact prepared destination payload. Replay does not reapply the current policy.
Event IDs
If normalized metadata supplies an event_id, Mammoth preserves it. Otherwise,
Mammoth generates deterministic SHA-256 fallback IDs:
evt_<64 lowercase hexadecimal characters>
txn_<64 lowercase hexadecimal characters>
The event fallback incorporates stable normalized CDC context, including the source, transaction and source positions, event sequence and time when available, schema, table, operation, row identity, and before/after values. The transaction fallback incorporates transaction context and the ordered child event IDs. Re-serializing the same normalized work therefore produces the same fallback ID, preserving retry and restart idempotency.
Treat IDs as opaque strings:
- do not parse the prefix or digest;
- do not reproduce Mammoth's hashing algorithm in a receiver;
- do not assume every ID is a UUID or a Mammoth-generated digest; and
- use an appropriate domain key instead when idempotency represents a business operation rather than one exact CDC event.
Mammoth provides at-least-once HTTP delivery, not global exactly-once effects. Independent operational-state stores and explicit operator replay can submit the same payload, so destination-side atomic deduplication remains required.
Payload evolution
Mammoth 1.x does not remove established fields or change their documented types and meanings incompatibly. Minor releases may add fields. PostgreSQL-derived columns are controlled by the source schema and are not frozen by Mammoth's version.
See Compatibility for the complete v1 promise and PostgreSQL for replica identity, schema evolution, and slot continuity requirements.