Manual Provisioning
The complete callback sequence when a cardholder types their card into a wallet, and exactly what you must return at each step.
Manual provisioning is when the cardholder opens Apple Pay or Google Pay and types the card number, expiry and CVV in by hand. Because anyone who can read a card number could do that, the cardholder must prove they own the card: either by entering a one-time code you deliver, or by logging in to your app, which the wallet opens for that purpose.
This is the path with the most work on your side. UnDosTres cannot complete it alone, because UnDosTres does not hold your cardholder's phone number or email address, and UnDosTres cannot log the cardholder in to your app. You do both.
This page covers the code route first, message by message, then verification in your app, which shares the start and the end of the sequence and replaces the middle.
The full sequence, code route
%%{init: {"sequence": {"actorMargin": 8, "boxMargin": 6, "noteMargin": 6, "messageMargin": 26, "width": 110, "diagramMarginX": 8, "diagramMarginY": 8}}}%%
sequenceDiagram
autonumber
participant CH as Cardholder
participant W as Wallet
participant MC as Mastercard MDES
participant UDT as UnDosTres
participant P as You
CH->>W: Types card number, expiry, CVV
W->>MC: Checks the card is eligible
MC-->>W: Terms and conditions
W-->>CH: Displays the terms
CH->>W: Accepts
W->>MC: Starts digitization
Note over MC,P: Step 1 - contact methods
MC->>UDT: Requests activation methods
UDT->>P: digitization.activationmethods
P-->>UDT: Masked phone and email
UDT-->>MC: Activation methods
MC-->>W: Options
W-->>CH: How do you want to verify
CH->>W: Chooses SMS
Note over MC,P: Step 2 - deliver the code
MC->>MC: Generates the activation code
MC->>UDT: Activation code
UDT->>P: digitization.activation
P->>CH: Sends the code by SMS
CH->>W: Enters the code
W->>MC: Submits the code
MC->>MC: Validates the code
Note over MC,P: Step 3 - done
MC->>MC: Activates the token
MC->>UDT: Digitization complete
UDT->>P: digitization.complete
P->>CH: Confirms card added
MC-->>W: Provisioning complete
W-->>CH: Card appears in the wallet
How the callbacks reach you
All four messages arrive as XML-RPC AdministrativeMessage calls to your endpoint, in the same shape as every other administrative message on this platform. See Administrative Message for the envelope.
Seven parameters, in order:
| # | Parameter | Notes |
|---|---|---|
| 1 | terminalID | Your terminal ID, 10 characters |
| 2 | reference | The wallet reference the message concerns |
| 3 | messageName | Which message this is. The four values below |
| 4 | messageData | The payload, KLV encoded |
| 5 | transactionID | Generated by the caller. Not unique, may repeat |
| 6 | transactionDate | ISO 8601 |
| 7 | checksum | HMAC-SHA256 of the method name concatenated with all argument values, keyed with your terminal password |
You respond to all of them with resultCode. 1 means accepted.
The four messages, and their full field-level reference:
1. digitization.activationmethods
digitization.activationmethodsWhen: at the very start, once the cardholder has accepted the terms.
What it means: a provisioning attempt has started and MDES needs to know how the cardholder can be reached, so the wallet can offer them a choice.
What you must return: the contact methods available for this cardholder. This is the one message where your response carries real data rather than just an acknowledgement.
KLV keys present: 910 digitized device id, 915 digitized token requestor id, 929 digitization path, 930 wallet recommendation, 931 tokenization pan source.
Activation methods
| Type | Method |
|---|---|
| 1 | Masked mobile phone number |
| 2 | Masked email address |
| 3 | Call to an automated call centre, initiated by the cardholder |
| 4 | Call to a staffed call centre, initiated by the cardholder |
| 5 | Website |
| 6 | Mobile application |
| 7 | Issuer voice call to the cardholder's phone |
Return only the methods you can actually service. If you return type 1, you must be able to send an SMS. If you return type 4, you must have a call centre that can authenticate a cardholder and act on the result.
Type 6 is your own app. It is the route described in Verifying in your app, and on this programme it is configured as the first method offered for both Google Pay and Apple Pay, with the SMS code second. Whether your response has to list it, or whether the scheme adds it from the product configuration UnDosTres maintains, is being confirmed; until then keep returning types 1 and 2 as shown below.
Mask the values you return. There is no scheme-mandated masking format, but the convention is:
- Email: keep the first two characters and the whole domain, star the rest.
ag*****@gmail.com- Phone: star everything except the last four digits, keeping any hyphens.
**-***-****-1234The masked value is displayed to the cardholder inside the wallet, so it has to be recognisable to them and useless to anyone else.
Response
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>resultCode</name>
<value><int>1</int></value>
</member>
<member>
<name>activationMethods</name>
<value>
<array>
<data>
<value>
<struct>
<member><name>type</name><value>1</value></member>
<member><name>value</name><value>555-444-2222</value></member>
</struct>
</value>
<value>
<struct>
<member><name>type</name><value>2</value></member>
<member><name>value</name><value>[email protected]</value></member>
</struct>
</value>
</data>
</array>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
If you return nothing here, provisioning stops. An empty or failed response means the wallet has no way to verify the cardholder, and the attempt dies. This is the single most common cause of a manual provisioning that silently never completes.
2. digitization.activation
digitization.activationWhen: after the cardholder picks a contact method: SMS, email or a voice call. When the cardholder picks your app instead, this message is not expected, because there is no code for you to deliver; see Verifying in your app.
What it means: MDES has generated the activation code. You deliver it to the cardholder over the channel they chose.
What you must return: resultCode only.
KLV keys present: 901 activation code, 902 activation method type, 903 activation method value, 904 activation expiry, 915 digitized token requestor id.
The code is generated and validated by MDES, not by you. You are the delivery channel. Do not generate your own code, do not modify it, and do not validate it. Read 901, send it, and acknowledge.
Note 904, the expiry. Deliver promptly; a code that arrives after it expires produces a digitization.event.Digitization_Exception and a confused cardholder.
3. digitization.complete
digitization.completeWhen: MDES has validated the code and activated the token.
What it means: the card is now live in the wallet.
What you must return: resultCode only.
KLV keys present: 254 digitized pan, 255 digitized wallet id, 910 device id, 911 pan expiry, 912 masked FPAN, 913 Token Unique Reference, 915 token requestor id, 923 event type, 924 event reason code.
This is your trigger to notify the cardholder. Wallet programmes require the issuer to tell the cardholder, over a channel they already trust, that their card was added. Send an in-app message, SMS or email on receipt of this callback. It is a launch requirement, not a nicety, and it is also the main way a cardholder finds out if someone else added their card.Store
913, the Token Unique Reference. It is the identifier you pass astoken_referencefor every lifecycle operation afterwards.
4. digitization.event.Digitization_Exception
digitization.event.Digitization_ExceptionWhen: the provisioning attempt failed.
What it means: one of the following happened:
- the activation code retries were exceeded
- an expired activation code was used
- an invalid activation code was used
- an incorrect activation code was entered
What you must return: resultCode only.
KLV keys present: the same set as digitization.complete.
Treat this as a closed, failed attempt. The cardholder may start again from scratch. If you are tracking provisioning funnels, this and digitization.complete are your two terminal outcomes.
Verifying in your app instead of with a code
The code is not the only way a cardholder can prove ownership. Both wallets can hand the cardholder over to your app, let you authenticate them the way you always do, with their login, PIN or biometrics, and take the result back. Google calls this app-to-app verification; Apple calls it In-App Verification. It is activation method type 6 in the table above, and it is the method cardholders prefer when it is offered, because nothing has to arrive by SMS.
On this programme the mobile application is configured as the first verification method for Google Pay and for Apple Pay, with the SMS code as the second. It is not an optional extra: a cardholder who has your app installed will be offered it first, and your app has to be able to finish the job.
Two rules shape the whole route:
- It is only offered on manual provisioning. When the cardholder starts from your own app, with push provisioning, you have already authenticated them, and the wallets do not allow your app to be offered as the verification step a second time.
- Your app must handle both the hand-off and the later case. A cardholder may pick your app in the wallet and be sent to it immediately, or may leave the card pending in the wallet and open your app on their own a day later. Both must end in an activated token, so your app checks for pending tokens at every login, not only when the wallet opens it.
The sequence, app route
%%{init: {"sequence": {"actorMargin": 8, "boxMargin": 6, "noteMargin": 6, "messageMargin": 26, "width": 110, "diagramMarginX": 8, "diagramMarginY": 8}}}%%
sequenceDiagram
autonumber
participant CH as Cardholder
participant W as Wallet
participant MC as Mastercard MDES
participant UDT as UnDosTres
participant APP as Your app
participant BE as Your backend
CH->>W: Types the card, accepts the terms
W->>MC: Starts digitization
MC->>UDT: Requests activation methods
UDT->>BE: digitization.activationmethods
BE-->>UDT: Masked phone and email
UDT-->>MC: Activation methods
MC-->>W: Options, including your app
W-->>CH: How do you want to verify
CH->>W: Chooses your app
Note over W,APP: Step A - hand-off
W->>APP: Opens your app with the token reference
APP->>APP: Checks the caller is the wallet
CH->>APP: Logs in
APP->>APP: Finds the pending token, checks ownership
Note over MC,BE: Step B - activation code
APP->>BE: Requests activation
BE->>UDT: GenerateTimeBasedSecret
UDT-->>BE: secret, valid 60s
BE-->>APP: card_id + secret
APP->>APP: Gets the activation code via the SDK
APP->>W: Returns the cardholder with the code
W->>MC: Submits the code
MC->>MC: Validates the code, activates the token
Note over MC,BE: Step C - done
MC->>UDT: Digitization complete
UDT->>BE: digitization.complete
BE->>CH: Confirms card added
MC-->>W: Provisioning complete
W-->>CH: Card appears in the wallet
Steps 1 to 9 are the same as the code route. Steps 10 to 21 replace digitization.activation and the SMS. Steps 22 to 26 are the same ending.
Steps A and B in detail, with the SDK
The two self-messages on "Your app" above are where the SDK does its work. This is the same stretch with the SDK as its own participant and the Android method names on the arrows. The iOS and React Native names are in the table that follows.
%%{init: {"sequence": {"actorMargin": 8, "boxMargin": 6, "noteMargin": 6, "messageMargin": 26, "width": 110, "diagramMarginX": 8, "diagramMarginY": 8}}}%%
sequenceDiagram
autonumber
participant W as Wallet
participant APP as Your app
participant SDK as SDK
participant BE as Your backend
participant UDT as UnDosTres
Note over W,SDK: Step A - hand-off
W->>APP: Intent: package, action, EXTRA_TEXT
APP->>APP: getCallingPackage() is com.google.android.gms
Note over APP: Cardholder logs in
APP->>SDK: getTokenInfo(intent)
SDK-->>APP: token reference, last four, state
APP->>APP: State NEEDS_IDENTITY_VERIFICATION, card is this cardholder's
Note over APP,UDT: Step B - activation code
APP->>BE: Activation request (card_id)
BE->>UDT: GenerateTimeBasedSecret
UDT-->>BE: secret, valid 60s
BE-->>APP: card_id + secret
APP->>SDK: getActivationCode(card_id, secret)
SDK->>UDT: Activation code request
UDT->>UDT: Verifies the secret
UDT-->>SDK: Activation code
SDK-->>APP: Activation code
APP->>SDK: activate(activity, activationCode)
SDK-->>W: Back to the wallet with the code
Note over W,UDT: On any failure: declineActivation(activity, DECLINE)
The card_id and secret go into the SDK's card data parameters, built with MppCardDataParameters.withCardSecret(cardId, cardSecret) on Android, exactly as for push provisioning. The SDK calls below are all on MeaPushProvisioning.GooglePay or MeaPushProvisioning.ApplePay, except the iOS pass lookups, which are on MeaPushProvisioning itself.
| Step | Android | iOS | React Native |
|---|---|---|---|
| Read the hand-off | Intent EXTRA_TEXT; getTokenInfo(intent, listener) parses it | appLaunchURL query: serialNumber, passTypeIdentifier, action=verify | The Intent arrives as a deep link; then GooglePay.getRegisteredTokens() |
| Find the pending token | getRegisteredTokens(...) or checkWalletForCardSuffix(lastFour, ...), state TOKEN_STATE_NEEDS_IDENTITY_VERIFICATION | secureElementPassWithSerialNumber(serialNumber) and remoteSecureElementPassWithSerialNumber(serialNumber) for the Watch, state requiresActivation | GooglePay.getRegisteredTokens(); ApplePay.secureElementPassWithSerialNumber(serialNumber) |
| Get the activation code | getActivationCode(cardParams, context, listener) | getActivationData(cardParams), returns activation data rather than a code | GooglePay.getActivationCode(cardData); ApplePay.getActivationData(cardData) |
| Hand back to the wallet | activate(activity, activationCode) | activate(secureElementPass, withActivationData:), once per pass found | GooglePay.activate(activationCode); ApplePay.activate(secureElementPass, activationData) |
| Decline | declineActivation(activity, ActivationResult.DECLINE) | No decline call is documented; leave the pass untouched and it stays pending | GooglePay.declineActivation('declined') |
| Server-side activation instead of a code | activate(tokenUniqueReference, paymentNetwork), then activate(activity) | activateSecureElementPass(secureElementPass, paymentNetwork) | GooglePay.activateWithTokenInfo(tokenInfo); ApplePay.activate(secureElementPass, paymentNetwork) |
What the wallet hands your app
| Google Pay, Android | Apple Pay, iOS | |
|---|---|---|
| How your app is opened | An explicit Android Intent to the package name and action configured for your app. The action is conventionally <your package>.a2a | Your app's custom URL scheme, the appLaunchURL configured for your pass |
| What identifies the token | EXTRA_TEXT: a Base64-encoded JSON carrying tokenUniqueReference and accountPanSuffix | Query parameters passTypeIdentifier, action=verify and serialNumber |
| If your app is not installed | The wallet opens your Play Store page. After installing, the cardholder restarts from the wallet | The wallet offers the other methods. Your SMS and email path remains the fallback |
| First thing your app checks | getCallingPackage() is com.google.android.gms. Anything else, abort | The launch came through your registered URL scheme with the parameters above |
What your app does, in order
- Accept the hand-off. Declare the Intent filter (Android) or the URL scheme (iOS) so the wallet can open your app, and read the token reference it passes. The SDK can parse the Android
EXTRA_TEXTfor you. - Verify the caller. Only the wallet may start this flow. On Android that is the calling package check above; on iOS, the URL scheme and parameters.
- Authenticate the cardholder with your standard login. This is the whole point of the route: the wallet is trusting your authentication in place of a code, so it must be the real one, not a cached session.
- Find the pending token and check it belongs to this cardholder. On Android the SDK lists the wallet's tokens; the one you want is in state
TOKEN_STATE_NEEDS_IDENTITY_VERIFICATIONand its last four digits match a card of the logged-in cardholder. On iOS, look the pass up byserialNumber, on the iPhone and on a paired Watch, and check its activation state isrequiresActivation. Never activate a token whose card is not linked to the authenticated user, whatever the wallet passed you. - Complete the activation. One of the two methods below.
- Return control to the wallet, with the result or with an explicit decline if any step above failed. The wallet finishes the provisioning and the token goes live.
For the later case, where the cardholder opens your app on their own, steps 1 and 2 do not apply: after login, look for pending tokens (step 4) and, if there is one, offer to activate it, showing which device it is on. Then continue from step 5.
Two ways to complete the activation
Activation code, build this one. Your app obtains a one-time activation code from the SDK and hands it back to the wallet, which submits it for validation. To produce that code the SDK needs the same card_id and secret pair that push provisioning uses, so your backend calls GenerateTimeBasedSecret exactly as described in Push Provisioning, with the same 60-second rule. If you build push provisioning, you have already built this half. On iOS the equivalent is activation data rather than a code; the SDK call differs, the inputs do not.
Server-side activation. Instead of a code, the token is activated directly: your app calls the SDK's server-side activation with the token reference, or your backend calls /token/activate with reason A, and your app then returns the cardholder to the wallet without a code. Tell us before relying on this method so it can be enabled and tested with you.
What arrives on your backend
| Message | On the app route |
|---|---|
digitization.activationmethods | Arrives as always, at the start. Return your contact methods as today |
digitization.activation | Not expected. Where a code is used, it is produced on your side through the SDK, so there is nothing for us to deliver |
digitization.complete | Arrives when the token is activated. Notify the cardholder as always |
Two details being confirmed. Whether youractivationmethodsresponse must list type 6, or whether the scheme adds your app from the product configuration; and which message, if any, you receive when your app declines the activation. Until confirmed, keep returning types 1 and 2, and treat a declined attempt as abandoned in your tracking.
What UnDosTres needs from you
The wallet only knows how to open your app because the scheme's product configuration says so, and UnDosTres maintains that configuration on your behalf. Send us, for each environment:
| Platform | Values |
|---|---|
| Android | Package name (applicationId) and the Intent action, in the form <package>.a2a |
| iOS | Bundle identifier and the custom URL scheme your app registers for the hand-off |
Nothing on this route can be tested until those values are configured, so send final values rather than placeholders.
Exact SDK calls. The provisioning SDK documents the method names and sample code for each platform: Android, section App-to-App Verification; iOS, section In-App Verification; and React Native, which carries both. This page tells you what to build and why; those pages tell you which method to call.
Tracking an attempt end to end
A provisioning attempt is started by digitization.activationmethods and finished by either digitization.complete or digitization.event.Digitization_Exception.
Store every incoming message with the timestamp you received it. You will need that for three things:
- Correlating messages to one attempt. Use the Token Unique Reference where present, or the card, to tie the sequence together.
- Abandoned provisioning. A cardholder who starts and never finishes should be reminded. See Use Cases and Runbook.
- Support. When a cardholder calls to say "it didn't work", the stored sequence tells you exactly which step it stopped at.
Related
- Push Provisioning — the path with no OTP, and the
GenerateTimeBasedSecretcall the app route reuses - Token Lifecycle Management — what happens after activation
- Use Cases and Runbook — abandoned and failed attempts
- KLV Lookup
Updated 5 days ago

