I'm following the documentation here: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Updating.html to update a created Apple wallet pass. I'm currently getting the registration endpoint and recording the deviceID and push_token. I'm left with a few questions:
- where are the pass type identifier and push token used in the APNs push? For the pass type identifier the doc say in cert, does that mean to add it to the jwt? If so in claims or headers? For the push token it says in communication with APNs. I can logically assume that means the payload, but just want to be sure.
- For the APNs push what is required in the fields
apns-push-type
,apns-topic
, andapns-priority
fields? My guesses would be the type is background, topic is the pass type identifier for the pass, and priority should not be included. But I haven't gotten it to work with code to that effect. - Is there a way to add the serial number of the pass to the push notification to skip the "Devices Ask for Changed Serial Numbers" step? It would complicate things significantly for my API to know which pass should be updated after losing context, and the user shouldn't have multiple passes for the same identifier from my app anyway.
- For the final step "Devices Ask for Latest Version of Passes" what format is the phone expecting the pass data in? It doesn't give any examples. My best guess would be json with a
pass-data
byte array representing a new signed pkpass with the same serial number but I can't find confirmation anywhere.
I'm currently creating and registering passes and recording the push token. Then sending a push notification in Go like this:
authorization, err := getAppleBearerAuth()
endpoint := fmt.Sprintf("https://api.sandbox.push.apple.com/3/device/%s", device_id) //APNs dev endpoint
//token in payload
payload := map[string]interface{}{"push_token": push_token}
payloadJSON, err := json.Marshal(payload)
if err != nil {
fmt.Println("Failed to marshal payload:", err)
return err
}
expiration := time.Now().Add(24 * time.Hour).Unix()
// Set the headers for the request
headers := map[string]string{
"Authorization": *authorization,
"Content-Type": "application/json",
"apns-push-type": "background",
"apns-topic": os.Getenv("APPLE_PASS_TYPE_IDENTIFIER"),
"apns-expiration": fmt.Sprintf("%d", expiration),
}
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // Set to false in production
},
},
}
request, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(payloadJSON))
if err != nil {
fmt.Println("Failed to create request:", err)
return err
}
for key, value := range headers {
request.Header.Set(key, value)
}
response, err := httpClient.Do(request)
if err != nil {
fmt.Println("Failed to send request:", err)
return err
}
defer response.Body.Close()
if response.StatusCode == http.StatusOK {
fmt.Println("Push notification sent successfully.")
} else {
fmt.Println("Failed to send the push notification. Status code:", response.StatusCode)
}
return nil}
func getAppleBearerAuth() (*string, error) {
kid := os.Getenv("APPLE_PUSH_KEY")
iss := os.Getenv("APPLE_TEAM_ID")
// Create a new JWT token
token := jwt.New(jwt.SigningMethodES256)
// Add claims
claims := token.Claims.(jwt.MapClaims)
claims["iss"] = iss
claims["iat"] = time.Now().Unix()
// Add kid to the header
token.Header["kid"] = kid
// Load your ES256 key
key, err := loadPrivateKey()
if err != nil {
return nil, err
}
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(key)
if err != nil {
return nil, err
}
// Add "Bearer " to the token string to make it a bearer token
bearer := "Bearer " + tokenString
return &bearer, nil
}
func loadPrivateKey() (interface{}, error) {
// Here you should implement loading your private key
// Usually it involves reading from a PEM file and parsing the key
// Path to the .p12 file
certPath := os.Getenv("APPLE_CERT")
// Password for the .p12 file
certPassword := os.Getenv("PASSKEYPASS")
p12Bytes, err := ioutil.ReadFile(certPath)
if err != nil {
return nil, err
}
blocks, err := pkcs12.ToPEM(p12Bytes, certPassword)
if err != nil {
return nil, err
}
pemData := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: blocks[0].Bytes,
})
key, err := jwt.ParseRSAPrivateKeyFromPEM(pemData)
if err != nil {
return nil, err
}
return key, nil
}
It is signed with the same certs the pass is created with. I can tell I'm probably not putting the pass type identifier in the right place, and it's likely why this step isn't working, but I'm not sure where it's supposed to be.
Has anyone managed to update Apple passes from a backend service that could clarify some of this?