PassKit's Messaging service can create and schedule a message for an existing pass. This reference shows how to target exactly one pass using either the REST API or the Go SDK/gRPC API.
The examples assume that your PassKit API or SDK client is already authenticated and configured for the region assigned to your account.
Terminology
Message
The title and plain-text body scheduled through the Distribution service for eligible wallet passes.
Payload
The request data used to create and schedule the message.
Programme ID
The PassKit Loyalty programme ID supplied as classId. Do not use a tier ID or template ID as classId.
PassKit ID
The PassKit-assigned ID of the individual pass. Use this ID as filterValue when targeting one pass.
Filter
The request structure that determines which pass receives the message.
Immediate message
A message with no displayFrom. It is scheduled for immediate processing and will normally be sent within 10 minutes.
Scheduled message
A message with displayFrom set to a future RFC3339 timestamp containing an explicit timezone offset or Z for UTC.
Create and schedule a message
The Distribution_addMessage operation creates and schedules the message in one request. There is no separate create-then-send operation for this workflow.
The Add Message request defines the recipient filter, message content, start time, and optional display expiry.
REST API
Operation:
Distribution_addMessageMethod and path:
POST /distribution/message
Use the endpoint for the region assigned to your PassKit account:
Europe:
https://api.pub1.passkit.io/distribution/messageUnited States:
https://api.pub2.passkit.io/distribution/message
The request uses JSON, the normal PassKit bearer authorisation, and this content type:
Content-Type: application/json
Send an immediate message with REST
The example below is for Membership/Loyalty passes. For Coupons and Event Ticket please adjust the Protocol value
Payload
{
"classId": "REPLACE_WITH_LOYALTY_PROGRAMME_ID",
"protocol": "MEMBERSHIP",
"filters": {
"filterGroups": [
{
"condition": "AND",
"fieldFilters": [
{
"filterField": "passkitId",
"filterValue": "REPLACE_WITH_PASSKIT_PASS_ID",
"filterOperator": "eq"
}
]
}
]
},
"title": "PassKit message test",
"body": "This is a test message. No action is required."
}
Omitting displayFrom schedules the message for immediate processing, and it will normally be sent within 10 minutes.
Processing and wallet delivery are asynchronous, so this does not guarantee that a device notification will appear within exactly 10 minutes. A successful API response confirms acceptance, not delivery.
Omitting displayUntil supplies no display expiry. The message remains available until it is cancelled or otherwise removed according to the applicable platform behaviour.
This request creates and schedules the message in one operation.
Send a scheduled message with REST
The example below is for Membership/Loyalty passes. For Coupons and Event Ticket please adjust the Protocol value
Payload
{
"classId": "REPLACE_WITH_LOYALTY_PROGRAMME_ID",
"protocol": "MEMBERSHIP",
"filters": {
"filterGroups": [
{
"condition": "AND",
"fieldFilters": [
{
"filterField": "passkitId",
"filterValue": "REPLACE_WITH_PASSKIT_PASS_ID",
"filterOperator": "eq"
}
]
}
]
},
"title": "PassKit message test",
"body": "This is a test message. No action is required.",
"displayFrom": "2027-01-15T09:00:00+07:00",
"displayUntil": "2027-01-31T23:59:59+07:00"
}
displayFrom controls when message processing should begin. displayUntil controls when the message should stop being displayed.
Replace both example timestamps. They must use RFC3339 and include an explicit numeric offset or Z. When both fields are supplied, displayUntil must be later than displayFrom. Scheduling does not guarantee an exact device-notification time.
Go SDK and gRPC API
The regional gRPC server address and RPC method are separate values:
Europe server:
grpc.pub1.passkit.io:443United States server:
grpc.pub2.passkit.io:443RPC method:
/io.Distribution/addMessage
The generated Go client method is:
DistributionClient.AddMessage(ctx, *io.Message) (*emptypb.Empty, error)
The following examples assume that ctx and an authenticated io.DistributionClient named distributionClient already exist.
Send an immediate message with the Go SDK
The example below is for Membership/Loyalty passes. For Coupons and Event Ticket please adjust the Protocol value
import "github.com/PassKit/passkit-golang-grpc-sdk/io"
message := &io.Message{
ClassId: "REPLACE_WITH_LOYALTY_PROGRAMME_ID",
Protocol: io.PassProtocol_MEMBERSHIP,
Filters: &io.Filters{
FilterGroups: []*io.FilterGroup{
{
Condition: io.Operator_AND,
FieldFilters: []*io.FieldFilter{
{
FilterField: "passkitId",
FilterValue: "REPLACE_WITH_PASSKIT_PASS_ID",
FilterOperator: "eq",
},
},
},
},
},
Title: "PassKit message test",
Body: "This is a test message. No action is required.",
}
response, err := distributionClient.AddMessage(ctx, message)
if err != nil {
return err
}
_ = response // *emptypb.Empty: API acceptance only
Do not automatically retry an Add Message request after a timeout or ambiguous transport failure. The original request may have been accepted even if the client did not receive the response. Verify the outcome before deciding whether another request is appropriate.
Send a scheduled message with the Go SDK
The SDK fields are protobuf timestamps. Parse the RFC3339 values and convert them with timestamppb.New:
The example below is for Membership/Loyalty passes. For Coupons and Event Ticket please adjust the Protocol value
import (
"time"
"github.com/PassKit/passkit-golang-grpc-sdk/io"
"google.golang.org/protobuf/types/known/timestamppb"
)
displayFrom, err := time.Parse(time.RFC3339, "2027-01-15T09:00:00+07:00")
if err != nil {
return err
}
displayUntil, err := time.Parse(time.RFC3339, "2027-01-31T23:59:59+07:00")
if err != nil {
return err
}
message := &io.Message{
ClassId: "REPLACE_WITH_LOYALTY_PROGRAMME_ID",
Protocol: io.PassProtocol_MEMBERSHIP,
Filters: &io.Filters{
FilterGroups: []*io.FilterGroup{
{
Condition: io.Operator_AND,
FieldFilters: []*io.FieldFilter{
{
FilterField: "passkitId",
FilterValue: "REPLACE_WITH_PASSKIT_PASS_ID",
FilterOperator: "eq",
},
},
},
},
},
Title: "PassKit message test",
Body: "This is a test message. No action is required.",
DisplayFrom: timestamppb.New(displayFrom),
DisplayUntil: timestamppb.New(displayUntil),
}
response, err := distributionClient.AddMessage(ctx, message)
if err != nil {
return err
}
_ = response // *emptypb.Empty: API acceptance only
The REST and Go examples represent the same scheduled instants. Protobuf timestamps normalise an instant independently of the offset used in the original RFC3339 text.
Payload field reference
Field | Required | Description |
classId | Yes | The Programme/Campaign/Event ID. |
protocol | Yes | "FLIGHT_PROTOCOL" "MEMBERSHIP" "SINGLE_USE_COUPON" "EVENT_TICKETING" |
filters | Yes | Contains the complete recipient selection. |
filterGroups | Yes | Contains exactly one filter group in this single-pass example. |
condition | Yes | Use AND. |
fieldFilters | Yes | Contains exactly one field filter in this single-pass example. |
filterField | Yes | for PassId - use passkitId. |
filterValue | Yes | The PassKit-assigned ID of the individual pass. |
filterOperator | Yes | Use eq for an exact pass-ID match. |
title | Yes | Message title. Maximum 35 characters. |
body | Yes | Plain-text message body. Maximum 500 characters. Do not use HTML, Markdown, or rich hyperlinks; include a URL as plain text when needed. |
displayFrom | No | RFC3339 start time. Omit it to request immediate processing. |
displayUntil | No | RFC3339 display expiry. Omit it to supply no display expiry. When used with displayFrom, it must be later. |
Target exactly one pass
This example is intentionally restricted to one pass. Changing, removing, or broadening the filter can change the recipient audience. Before submitting, verify the Loyalty programme ID, PassKit-assigned pass ID, complete recipient filter, title, body, scheduled time, and optional display expiry.
Google Wallet prerequisite
To display stored messages in Google Wallet, the pass template must contain the Messages field. Add the Messages field using the PassKit Portal designer and save the design. See Add the Message Field to Your Google Wallet Design.
Successful responses
REST response
{}This empty response means the Add Message request was accepted by the API.
Go SDK and gRPC response
Successful completion returns an empty protobuf response:
*emptypb.Empty
The REST {} response and Go/gRPC *emptypb.Empty response confirm API acceptance. Neither response proves device delivery or that a wallet notification appeared, and neither is a device-delivery receipt. Message delivery is asynchronous. Portal status and recipient counts must not be treated as device-delivery receipts.
