Message shapes

Sequin converts Postgres changes and rows into JSON messages that are delivered to your sinks.

When you setup a sink, you can choose whether the sink should receive change messages or row messages.

Change messages

A change message has this shape:

{
  record: {
    [key: string]: any;  // The row data
  };
  changes: null | {
    [key: string]: any;  // Previous values for changed fields (updates only)
  };
  action: "insert" | "update" | "delete" | "read";
  metadata: {
    table_schema: string;
    table_name: string;
    commit_timestamp: string;  // ISO timestamp
    commit_lsn: integer;  // Logical replication LSN of the transaction
    transaction_annotations: null | {
      [key: string]: any;  // User-provided transaction context
    };
    sink: {
      id: string;
      name: string;
    };
  };
}

Change message fields

record
object

The current state of the row. Contains all column values.

changes
object

For update operations, contains the previous values of changed fields. For insert and delete operations, this field is null.

action
string

The type of change that occurred. One of:

  • insert: A new row was created
  • update: An existing row was modified
  • delete: A row was deleted
  • read: A row was read during a backfill
metadata
object

Additional context about the change.

metadata.table_schema
string

The Postgres schema containing the table (e.g., “public”).

metadata.table_name
string

The name of the table that was changed.

metadata.commit_timestamp
string

ISO 8601 timestamp when the change was committed to the database.

metadata.transaction_annotations
object

User-provided context about the transaction. Contains arbitrary JSON data set via pg_logical_emit_message. See Transaction annotations for details.

metadata.sink
object

Information about the sink receiving this message.

metadata.sink.id
string

Unique identifier for the sink.

metadata.sink.name
string

Name of the sink.

For update operations, record contains the new values and changes contains the previous values of only the fields that changed. For all other operations, changes is null.

Row messages

A row message has this shape:

{
  record: {
    [key: string]: any;
  };
  metadata: {
    table_schema: string;
    table_name: string;
    sink: {
      id: string;
      name: string;
    };
  };
}

Row message fields

record
object

The current state of the row. Contains all column values.

metadata
object

Additional context about the row.

metadata.table_schema
string

The Postgres schema containing the table (e.g., “public”).

metadata.table_name
string

The name of the table.

metadata.sink
object

Information about the sink receiving this message.

metadata.sink.id
string

Unique identifier for the sink.

metadata.sink.name
string

Name of the sink.

For example:

{
  "record": {
    "id": 1,
    "name": "Paul Atreides",
    "title": "Duke of Arrakis",
    "spice_allocation": 1000,
    "is_kwisatz_haderach": true
  },
  "metadata": {
    "table_schema": "public", 
    "table_name": "house_atreides_members",
    "sink": {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "name": "dune_characters_kafka"
    }
  }
}

At the moment, row messages only support insert and update operations. Soon, we’ll support delete operations by adding a deleted boolean column to the message.

Which message shape should you use?

Change messages are the best fit for most use cases. They contain more information about discrete changes to a row and include delete operations.

If you only need the latest version of a row, and either don’t need deletes or soft-delete rows, consider using row messages. The nice part about row messages is that every message is the same shape, whether it’s an insert, update, or backfill (“read”) message.