Receiving and Verifying Card States from the Samsung Wallet Server

Mobassir Ahsan

Engineer, Samsung Developer Program

The previous tutorial, Implementing "Add to Wallet" in an Android Application, showed how to generate and sign a card data token to add the card to Samsung Wallet. This tutorial demonstrates how you can perform server interactions with the Samsung Wallet server and retrieve information such as the card states on a user’s device.

If you are a Samsung Wallet partner who is offering Samsung Wallet cards to your users, you might also want to know how you can track a provided wallet card’s status on a user’s device. Follow along in this tutorial to learn how you can utilize the Send Card State API and retrieve this information to your own server. All code examples used in this tutorial can be found within the sample code provided at the end of this tutorial for further reference.

Card states and the Send Card State API

The Samsung Wallet card’s status on a user’s device is represented by various states, such as ADDED, UPDATED, or DELETED.

Whenever the card state of a card changes on a user’s device, Samsung Wallet server sends a notification to the configured partner server informing about the change. This API provided by Samsung is called the Send Card State API.

undefined
Figure 1: Samsung Wallet Card state changes

Samsung provides the Send Card State API as a means of server-to-server communication between the Samsung server and the partner’s server and to let the partner know about the card state of their issued cards on user’s devices. With this API, partners can track the state of a wallet card on a user’s Samsung Galaxy device.

Prerequisites

Before you can test the Send Card State API, you need to:

  1. Complete the Samsung Wallet onboarding process.
  2. Create a Samsung Wallet card template.
  3. Launch the wallet card template and have it in VERIFYING or ACTIVE status so that the card can be added to a user’s device.
  4. Have an existing server to receive the notifications. You can use CodeSandbox or a similar online hosting service for testing.
  5. Configure your firewall (if you use any) to accept incoming connections from the Samsung Wallet server (34.200.172.231 and 13.209.93.60).

When you have completed all the prerequisites, proceed to the next step to configure your wallet card template to send requests to your server.

Configure the Wallet card template for the Send Card State API

To receive the Send Card State notifications on your server, you need to set your server’s URL in the desired Samsung Wallet card’s options:

  1. Go to the Wallet Partners Portal.
  2. From the Wallet Cards dropdown, select “Manage Wallet Card.”
  3. Click the name of the wallet card.
  4. Click “Edit” and then scroll down to the “Partner Send card state” section to modify the Partner server URL.
  5. Click “Save” to set the partner server URL for the card.
undefined
Figure 2: Partner Send card state URL input field

Now, whenever a user adds or deletes an issued Samsung Wallet card to their device, the Samsung Wallet server automatically sends a POST notification to the Partner server URL set in the Wallet Partners Portal. Next you need to learn about the specification of the request so that you can handle it from the server.

Send Card State API specification and format

For a complete description of the Send Card State API specification, see Samsung Wallet documentation.

Request method

The Send Card State API uses a POST method to send a request to the server. The API path for the request is fixed and uses the partner server URL that you defined in section “Configure the Wallet card template for the Send Card State API.”

API path and URL parameters

The API path at the very end of the "Partner Send card state" section is the path where the Samsung server sends the Send card state POST request. So the complete API path URL is: {Partner server URL}/cards/{cardId}/{refId}?cc2={cc2}&event={event}.

Here, cardId is the Card ID of the wallet card template and refId is the reference ID field of the issued card data, which is a unique identifier. The cc2 query parameter is the 2-letter country code (ISO 3166-1 alpha-2) and event is the card state event (ADDED, DELETED, or UPDATED) occurring in the user’s device.

Consider the following example card configuration:

  • Partner server URL: https://partner.server.url
  • Card id: 123
  • Ref id for the issued card: abc
  • Country code: US

In this configuration, whenever the user adds the card to their Samsung Wallet application, the Samsung Wallet server sends a Send card state notification to the following URL:
https://partner.server.url/cards/123/abc?cc2=US&event=ADDED.

Similarly, if a user from the United Kingdom deletes a card with the refId xyz, the POST request is sent to https://partner.server.url/cards/123/xyz?cc2=GB&event=DELETED.

Therefore, you can know if a card was added or removed from the user’s device directly from the query parameters.

POST request body

The POST request body does not contain any information regarding the card state. Rather it just provides a callback URL that you can use if you want to send an update notification for the card.

{
  "callback": "https://us-tsapi.walletsvc.samsung.com"
}

POST request header

The POST request header contains all the required information for ensuring the authenticity of the request. It contains a request ID with the name “x-request-id” and a JWT bearer token credential for authentication with the name “Authorization” in the header.

The Samsung Wallet server uses a bearer authorization token to ensure the authenticity of the requests being sent to the partner server. For details of the security factors, see Authorization Token.

The bearer token is encoded in base64 following the JWT specification. It has three parts: JWS Header containing authentication related information, JWS Payload containing the API path, method, and refID, and JWS Signature, which validates that the bearer token is signed by the Samsung server.

JWS Header format:

{
  "cty": "AUTH", // Always “AUTH”
  "ver": "3", // Can also be “2” for legacy card data token
  "partnerId": "4048012345678938963", // Your partner ID
  "utc": 1728995805104, // Time of signing in milliseconds
  "alg": "RS256",
  "certificateId": "A123" // Only provided for token version 3
}

JWS Payload format:

{
  "API": 
  {
    "path": "/cards/3h844qgbhil00/2e19cd17-1b3e-4a3a-b904?cc2=GB&event=ADDED",
    "method": "POST"
  },
  "refId": "2e19cd17-1b3e-4a3a-b904-f30dc91ac264"
}

Finally, the bearer token contains a signature to verify the token. This is signed using the Samsung Private Key and can be validated using the Public Key provided by Samsung Wallet during the onboarding process.

After receiving any request from the Samsung Wallet server, your server should send back an HTTP status code as a response. Samsung Server expects one of the following codes as a response:

  • 200 OK
  • 401 Unauthorized
  • 500 Internal Server Error
  • 503 Service Unavailable

This is the complete specification of the Send Card State API that you need to be aware of before you implement the server.

Next, you need to configure your server to accept the POST request in the specified format.

Configure the Spring server to receive the POST request

To receive and interpret the Send Card State POST notifications sent by the Samsung Wallet server, you need to configure a partner server and host the server at the URL you specified earlier.

To receive the POST requests, this tutorial extends an existing server created using the Spring Boot framework. If you want to know how the Spring server is configured, check out the “Generate signed Wallet card data” section in the Implementing "Add to Wallet" in an Android Application tutorial. This CData generation server is used as the base server application for this tutorial, so the dependencies are the same as well. Now you can start implementing the tutorial.

Create a controller class to intercept the POST request

Samsung Wallet always sends the Send card state POST notification to the fixed API path URL: {Partner server URL}/cards/{cardId}/{refId}.

  1. Create a new controller class in your Spring server to intercept any POST request that is sent to this API path.
    @RestController
    @RequestMapping("/cards")
    class CardStateController {
      @PostMapping(path = ["/{cardId}/{refId}"])
      fun handleCardState(@PathVariable cardId: String,
                          @PathVariable refId: String): HttpStatusCode {
          // Implement your logic here to process the card state.
    
          println("Received card state notification for card ID $cardId and reference ID $refId.")
          
          return HttpStatus.OK
      }
    }
    
    
  2. Run the server and then add or delete a card from your Samsung Wallet.

    If the partner server URL was set correctly in section “Configure the Wallet card template for the Send Card State API,” your server should receive a POST request from the Samsung server and print the following message to the console: “Received card state notification.”

Update the controller class to receive the query parameters

  1. Handle the query parameters from the request by adding the following parameters as the function’s parameters: @RequestParam("cc2") cc2: String, @RequestParam("event") event: String
  2. Receive and print the request body using the @RequestBody body: String parameter.

The function should now look like this:

@PostMapping(path = ["/{cardId}/{refId}"], params = ["cc2", "event"])
fun handleCardState(@PathVariable cardId: String,
                   @PathVariable refId: String,
                   @RequestParam("cc2") cc2: String,
                   @RequestParam("event") event: String,
                   @RequestBody body: String): HttpStatusCode {

   // Implement your logic here to process the card state.

   println("Country code: $cc2")
   println("Wallet Card State Event: $event")

   println("Request body: $body")

   return HttpStatus.OK 
}

Now whenever the Samsung server sends a request to the server, it prints the device’s country code and the wallet card’s state event on the device.

Verify the POST request

This is the final and the most important step of this tutorial. Before accepting any incoming POST request, you should always validate the request by following the API specification mentioned earlier in the tutorial.

The security procedures can include but are not limited to:

  • Matching your PartnerID with the received partnerId custom parameter.
  • Checking the token version with the ver custom parameter. For token version 3, match your CertificateID using the certificateId custom parameter.
  • Checking the time of signing using the utc custom parameter.
  • Matching the other JWS Header parameters with the values mentioned in the specification.
  • Matching the Path from the JWS Payload with the received URL.
  • Verifying the JWT.

This section shows how you can implement each of these one by one.

First, parse the authentication token and read the header.

val signedJWT : SignedJWT = SignedJWT.parse(authToken)
val jwsHeader : JWSHeader = signedJWT.header

Match PartnerId and JWS Header parameters:

val ownPartnerId = "4048012345678938963" // Your partner ID from Samsung Wallet Partner Portal
val receivedPartnerId = jwsHeader.customParams["partnerId"]
val cType = jwsHeader.contentType
val alg = jwsHeader.algorithm.name

// Check if the JWS header parameters match the expected values
if (cType == "AUTH" && alg == "RS256" && receivedPartnerId == ownPartnerId ) {
    println("JWS Header parameters matched")
    // Proceed with further verification
}

Check the token version and match CertificateId:

val ver = jwsHeader.customParams["ver"]
val ownCertificateId = "A123" // Your certificate ID from Samsung Wallet Partner Portal
val receivedCertificateId = jwsHeader.customParams["certificateId"]?: ""

// If partner uses token version 3 in the JWS header of the CDATA,
// Then Samsung server also returns version 3 response along with the certificate ID
if(ver == "3" && receivedCertificateId == ownCertificateId){
    println("JWS Header certificate ID matched")
    // Proceed with further verification
}

Check if the token was generated recently:

// Check if the timestamp is within acceptable range
val utc = jwsHeader.customParams["utc"] as Long
val timeDelta = System.currentTimeMillis() - utc
println("Time Delta: $timeDelta")
if (timeDelta < 600000L) {
    println("UTC Timestamp is within last 1 minute. Time delta = $timeDelta ms.")
    // Proceed with further verification
}

Match the API path with the received API path from the payload:

val receivedAPIValue = signedJWT.payload.toJSONObject()["API"]?.toString()?: ""
val receivedAPIPath = receivedAPIValue.substring(6, receivedAPIValue.length - 14)
val expectedPath = "/cards/$cardId/$refId?cc2=$cc2&event=$event"

// Match the path in the payload with the expected path
if (receivedAPIPath == expectedPath) {
    println("Path matched")
    // Proceed with further verification
}

Finally, validate the token using the Samsung Certificate provided to you during the onboarding process:

  1. Read the Samsung Certificate from a file and then extract the public key. For instructions, refer to the CData generation server sample code at Implementing "Add to Wallet" in an Android Application.
  2. Build an RSAKey object using the extracted public key.
  3. Create an RSASSAVerifier object with the RSAKey to verify the token.
  4. Verify the token using the verifier.
// Verify the signature of the JWT token using the public key provided by Samsung Wallet.
val samsungPublicKey = readCertificate(getStringFromFile("sample/securities/Samsung.crt"))
val rsaKey = RSAKey.Builder(samsungPublicKey as RSAPublicKey).build()
val verifier: RSASSAVerifier = RSASSAVerifier(rsaKey)

if(signedJWT.verify(verifier)){
    println("Verification successful")
    // Implement your logic here to process the card state notification.
    // For example, you can update the card status in your database or trigger a notification to the user.
    // In this example, we simply return a 200 OK response indicating that the notification was successfully processed.
    return HttpStatus.OK
} else {
    println("Verification Failed")
    // Return an appropriate HTTP status code indicating that the notification could not be verified.
    return HttpStatus.UNAUTHORIZED
}

Now the complete implementation of the Controller class to receive and verify the Send card state request is complete. Once a Send card state request is completely verified, you can accept the request as a valid card state update and make any changes as required. For example, you can update the card status information in your own database or trigger a notification to the user.

Summary

By completing this tutorial, you are now able to receive card state updates from the Samsung Wallet server using the Send Card State API and validate them. In a future tutorial, we will discuss how you can expand the server interaction functionality even further and how you can update Samsung Wallet card information on user devices through the Get Card Data API.

If you want to discuss or ask questions about this tutorial, you can share your thoughts or queries on the Samsung Developers Forum or contact us directly for any implementation-related issues through the Samsung Developer Support Portal. If you want to keep up-to-date with the latest developments in the Samsung Developers Ecosystem, subscribe to the Samsung Developers Newsletter.

Sample Code

You can click on the link given below to download the complete sample code used in this tutorial.

Wallet Card State Server Sample Code
(55 KB) Dec 2024

Additional resources

Preferences Submitted

You have successfully updated your cookie preferences.