Consuming Interactions data via Pub/Sub
Connecting to Pub/Sub
Downstream destinations can choose to build a custom application to pull and process Interactions data from Pub/Sub or utilize Google's cloud-native solutions.
Building your own subscriber
Google provides client libraries in many languages with documentation and examples as well as documentation around best practices. Be sure to review this documentation if building custom application logic to subscribe to Interactions Pub/Sub data.
Useful Links
Dataflow
Google also offers a fully-managed, scalable streaming platform called Dataflow that natively connects to Pub/Sub for comprehensive data streaming solutions.
Dataflow can be used to build comprehensive pipelines that can help you with:
- Ingesting streaming data from Pub/Sub, transforming it, and loading it into BigQuery, Cloud Storage, or other sinks.
- Consuming event data from Pub/Sub and performing analytics or triggering workflows.
Useful Links
Note: Google recommends specific Pub/Sub subscription settings when using Dataflow. Please let MIG know if you're planning on using Dataflow so subscriptions can be set up accordingly.
Pub/Sub Subscription configuration
There are many configuration options available in Pub/Sub. Retry policies, deadlettering, and message acknowledgement deadlines are some examples. Please reach out to DDx API Support at [email protected] or in a joint Slack channel with questions or requests regarding Pub/Sub configuration.
Direct BigQuery Subscriptions
If you have a BigQuery table ingesting messages from the Interactions API using a BigQuery subscription, it is recommended to set the column accepting the message body (data) or JSON objects within the message body to the STRING data type to prevent potential data loss. data may contain special characters which cannot be parsed by BigQuery's JSON column type; messages that fail to parse will bypass the specified table and instead be forwarded to the associated deadletter topic.
Data Format in Pub/Sub
Data is sent to destinations via Pub/Sub in format identical to the Interactions API's /interactions POST endpoint with a light wrapper that includes useful metadata about the data within.
Note that the jsonMetadata property is percent-encoded as a string and is not standard JSON.
{
"source": {
"vendorKeyId":"32",
"vendor": "Voter Tool XYZ",
"organizationName": "Upstream Org for America",
"committeeName": "Poppy for Governor",
"state": "MA"
},
"correlationId": "11534196688560387915",
"messageIngressTimeUtc": "2025-09-19T15:11:22.199771Z",
"interaction": {
"person": [
{
"id": "12345",
"type": "rallyPersonId"
},
{
"id": "89076",
"type": "dwid"
}
],
"channel": {
"type": "phone_call",
"value": "\u002B11234567890"
},
"stateCode": "CO",
"attemptDateTime": "2025-09-10T22:11:48.662Z",
"method": "text",
"committee": [
{
"type": "MyCRM",
"id": "poppy-for-governor"
}
],
"canvasser": [
{
"type": "rallyUserId",
"id": "99999"
}
],
"vendorSource": "Blue Text",
"outcome": "successful_contact",
"outcomesDetailed": [
{
"type": "survey_response",
"value": {
"questionId": null,
"questionText": "Are you able to attend the Weekend Rally in Cambridge?",
"responseId": null,
"responseText": "yeah"
}
},
{
"type": "activist_code",
"value": {
"activistCodeId": null,
"text": "123"
}
},
{
"type": "other_type",
"value": "321",
"moreInfo": {
"dayOfWeek": "wed",
"otherProp": "yes"
}
}
],
"threadId": null,
"jsonMetadata": "{\n \u0022event\u0022: \u0022weekend canvass cambridge\u0022,\n \u0022rsvp\u0022: \u0022no\u0022\n}"
}
}{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "InteractionPublishRequest",
"type": "object",
"description": "Request object for publishing an interaction to Pub/Sub.",
"additionalProperties": false,
"properties": {
"source": {
"x-nullable": true,
"$ref": "#/definitions/InteractionSource"
},
"correlationId": {
"type": "string"
},
"messageIngressTimeUtc": {
"type": "string",
"format": "date-time"
},
"interaction": {
"x-nullable": true,
"$ref": "#/definitions/InteractionDto"
}
},
"definitions": {
"InteractionSource": {
"type": "object",
"additionalProperties": false,
"properties": {
"vendorKeyId": {
"type": "string"
},
"vendor": {
"type": "string"
},
"organizationName": {
"type": "string"
},
"committeeName": {
"type": "string"
},
"state": {
"type": "string"
}
}
},
"InteractionDto": {
"type": "object",
"description": "Represents a single interaction record containing voter outreach attempt data.",
"additionalProperties": false,
"required": [
"person",
"stateCode",
"attemptDateTime",
"method",
"committee",
"vendorSource",
"outcome"
],
"properties": {
"person": {
"type": "array",
"description": "Array of person identifiers representing all IDs from any vendor system for the person interacted with.\nEach person can contains an ID and type of ID indicating source system.\nAt least one ID is required, maximum of 100 IDs per interaction.",
"items": {
"$ref": "#/definitions/PersonIdentifier"
}
},
"channel": {
"description": "Channel-of-contact details used during the outreach attempt.\nIf the `type` is recognized as an Address Channel (type = \"address\"), the `value` will be deserialized to that type.\nIf the `type` is unrecognized, the payload will be deserialized to an unvalidated, loosely typed object for flexibility.",
"x-nullable": true,
"$ref": "#/definitions/IChannelDetails"
},
"stateCode": {
"type": "string",
"description": "2-character US state postal abbreviation (e.g., AL, WY).\nMust be a valid uppercase state code.",
"maxLength": 2,
"minLength": 2
},
"attemptDateTime": {
"type": "string",
"description": "The date and time when the outreach attempt occurred, as reported by the vendor\nand normalized to Coordinated Universal Time (UTC).",
"format": "date-time",
"minLength": 1
},
"method": {
"description": "The standardized communication channel used to reach a constituent during an outreach attempt.\nExamples include phone, door, SMS, email, etc.",
"$ref": "#/definitions/ContactMethod"
},
"committee": {
"type": "array",
"description": "Array of entities (campaign, state party, etc.) that conducted or logged the outreach attempt.\nCommittee objects consist of a type and an identifier, and at least one committee must be provided. ",
"items": {
"$ref": "#/definitions/CommitteeDetails"
}
},
"canvasser": {
"type": "array",
"description": "Array of canvasser details for the individual who conducted the outreach attempt, as reported by the vendor.\nOptional object containing identifier and identifier type.",
"x-nullable": true,
"items": {
"$ref": "#/definitions/CanvasserDetails"
}
},
"vendorSource": {
"type": "string",
"description": "Canonical name of the vendor or tool used to facilitate the outreach attempt.\nThis field reflects the platform through which the contact was made, not necessarily where the data was ingested from.",
"minLength": 1
},
"outcome": {
"description": "Standardized result of the outreach attempt, representing what happened during the interaction.\nExamples include successful_contact, not_home, refused, etc.",
"$ref": "#/definitions/Outcome"
},
"outcomesDetailed": {
"type": "array",
"description": "Optional array of objects that provide additional context around the results of an interaction, such as activist codes or survey responses.\nIf the type is recognized as an Activist Code Outcome (type = \"activist_code\")\nor Survey Response Outcome (type = \"survey_response\") it will be deserialized to that type.\nIf the type is unrecognized, it will be deserialized to an unvalidated, loosely typed object for flexibility.",
"x-nullable": true,
"items": {
"$ref": "#/definitions/IOutcomeDetails"
}
},
"threadId": {
"type": "string",
"description": "Unique identifier for interactions that took place during the same conversation or set of conversations.\nUsed to group related interactions together. Maximum length of 100 characters.",
"maxLength": 100,
"minLength": 0,
"x-nullable": true
},
"jsonMetadata": {
"type": "string",
"description": "Additional metadata in JSON format related to the interaction.\nMust be valid JSON when provided. Can contain arbitrary structured data relevant to the interaction.",
"x-nullable": true
}
}
},
"PersonIdentifier": {
"type": "object",
"description": "Represents a person involved in an interaction, including their identifier and optional contact information.",
"additionalProperties": false,
"required": [
"id",
"type"
],
"properties": {
"id": {
"type": "string",
"description": "A primary identifier for tracking individual voters. This can be an integer (e.g. for DNC person_ids),\na string (for UUIDs, phone numbers), or other identifier formats depending on the source system.\nUsed to link voter records across different data sources and track in-state voter history over time.",
"minLength": 1
},
"type": {
"type": "string",
"description": "Type of identifier or source system that generated the id.\nExamples include \"VAN\", \"phone\", \"slimCRM\", \"DNC\", \"SOS\", etc. \nThis field indicates the origin or authority of the person identifier.",
"minLength": 1
}
}
},
"IChannelDetails": {
"type": "object",
"discriminator": {
"propertyName": "type"
},
"x-abstract": true,
"additionalProperties": false,
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "Type of the channel specified in 'value'. E.g: a phone number, address, email address, social media handle, etc.\nRequired when Channel is provided."
},
"id": {
"type": "string",
"description": "Optional; unique identifier for any known id for the channel `type`",
"x-nullable": true
},
"value": {
"description": "The actual channel used during the outreach attempt.\nExamples: +15551234567, [email protected], @handle. If `address`, pass in address object. Required when Channel is provided."
}
}
},
"AddressChannelDetails": {
"allOf": [
{
"$ref": "#/definitions/ChannelDetailsOfAddress"
},
{
"type": "object",
"description": "Represents an address used to contact someone for an interaction.",
"additionalProperties": {},
"required": [
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"minLength": 1
},
"value": {
"$ref": "#/definitions/Address"
}
}
}
]
},
"Address": {
"type": "object",
"additionalProperties": false,
"properties": {
"addressLine1": {
"type": "string"
},
"addressLine2": {
"type": "string",
"x-nullable": true
},
"city": {
"type": "string"
},
"state": {
"type": "string"
},
"postalCode": {
"type": "string"
}
}
},
"ChannelDetailsOfAddress": {
"type": "object",
"x-abstract": true,
"additionalProperties": {},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"minLength": 1
},
"id": {
"type": "string",
"x-nullable": true
},
"value": {
"$ref": "#/definitions/Address"
}
}
},
"ContactMethod": {
"type": "string",
"description": "",
"enum": [
"unknown",
"mail",
"letter",
"digital_ad",
"email",
"text",
"text_broadcast",
"robo_call",
"dialer_call",
"phone_call",
"door_knock",
"Event",
"hot_spot",
"one_on_one"
]
},
"CommitteeDetails": {
"type": "object",
"description": "Represents details for the entities that conducted or logged the outreach attempt.",
"additionalProperties": false,
"required": [
"type",
"id"
],
"properties": {
"type": {
"type": "string",
"description": "Type of the committee identifier specified in 'id', representing the source context of the interaction.",
"minLength": 1
},
"id": {
"type": "string",
"description": "The actual committee identifier itself.",
"minLength": 1
}
}
},
"CanvasserDetails": {
"type": "object",
"description": "Represents canvasser details for the individual who conducted the outreach attempt.",
"additionalProperties": false,
"required": [
"type",
"id"
],
"properties": {
"type": {
"type": "string",
"description": "Type of the canvasser identifier specified in 'id'.\nRequired when Canvasser is provided.",
"minLength": 1
},
"id": {
"type": "string",
"description": "The actual canvasser identifier used during the outreach attempt.\nExamples: user ID, employee number, volunteer ID, etc.\nRequired when Canvasser is provided.",
"minLength": 1
}
}
},
"Outcome": {
"type": "string",
"description": "",
"enum": [
"unknown",
"clicked_link",
"email_opened",
"submitted_form",
"successful_contact",
"deceased",
"deliverability_error",
"permanently_undeliverable",
"inaccessible",
"language_barrier",
"moved",
"no_answer",
"wrong_number",
"hostile",
"opted_out",
"marked_spam",
"refused",
"delivered",
"lit_drop",
"left_message",
"other"
]
},
"IOutcomeDetails": {
"type": "object",
"discriminator": {
"propertyName": "type"
},
"x-abstract": true,
"additionalProperties": false,
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "This field categorizes the type of detailed outcome information being provided."
},
"value": {}
}
},
"ActivistCodeOutcomeDetails": {
"allOf": [
{
"$ref": "#/definitions/OutcomeDetailsOfActivistCode"
},
{
"type": "object",
"description": "Represents an activist code (e.g. affiliation, activity, or interest) acquired during an interaction.",
"additionalProperties": {},
"required": [
"value"
],
"properties": {
"type": {
"type": "string"
},
"value": {
"$ref": "#/definitions/ActivistCode"
}
}
}
]
},
"ActivistCode": {
"type": "object",
"additionalProperties": false,
"required": [
"text"
],
"properties": {
"activistCodeId": {
"type": "string",
"description": "Unique identifier for an activist code;\nthe value may be retrieved from the system that sourced the activist code ",
"x-nullable": true
},
"text": {
"type": "string",
"description": "The question or information prompted during the interaction",
"minLength": 1
}
}
},
"OutcomeDetailsOfActivistCode": {
"type": "object",
"x-abstract": true,
"additionalProperties": {},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "This field categorizes the type of detailed outcome information being provided.",
"minLength": 1
},
"value": {
"$ref": "#/definitions/ActivistCode"
}
}
},
"SurveyResponseOutcomeDetails": {
"allOf": [
{
"$ref": "#/definitions/OutcomeDetailsOfSurveyResponse"
},
{
"type": "object",
"description": "Represents a survey question and response obtained from an interaction.",
"additionalProperties": {},
"required": [
"value"
],
"properties": {
"type": {
"type": "string"
},
"value": {
"$ref": "#/definitions/SurveyResponse"
}
}
}
]
},
"SurveyResponse": {
"type": "object",
"additionalProperties": false,
"required": [
"questionText",
"responseText"
],
"properties": {
"questionId": {
"type": "string",
"description": "Unique identifier for the survey question being asked;\nthe value may be retrieved from the system that sourced the question ",
"x-nullable": true
},
"questionText": {
"type": "string",
"description": "The question text presented during an interation",
"minLength": 1
},
"responseId": {
"type": "string",
"description": "Unique identifier for the survey response associated with the question;\nthe value may be retrieved from the system that sourced the question ",
"x-nullable": true
},
"responseText": {
"type": "string",
"description": "An answer to the question.\nRequired when Survey Question text is provided.",
"minLength": 1
}
}
},
"OutcomeDetailsOfSurveyResponse": {
"type": "object",
"x-abstract": true,
"additionalProperties": {},
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "This field categorizes the type of detailed outcome information being provided.",
"minLength": 1
},
"value": {
"$ref": "#/definitions/SurveyResponse"
}
}
}
}
}Updated 21 days ago