Digital Wallet Push Provisioning
Add a Virtual Card to Apple Pay / Google Pay
Use the getCardProvisioningUrl query to mint a short, single-use URL that — when opened on your end-user's phone — launches Fluz on the device and adds the virtual card for a given offer to Apple Pay (iOS) or Google Pay (Android).
You can deliver the URL however you like: a QR code, an SMS, an email, or an in-app button. Fluz handles the wallet-provisioning flow from there.
When to use it
Anywhere you would normally hand the user a virtual card and want them to be able to tap it in their phone's wallet without typing the card number. Typical flows:
- After creating a card offer for the user, present a QR code on screen.
- Text or email the URL to the cardholder.
- Embed an "Add to Apple Pay / Google Pay" button in your own mobile app.
Prerequisites
- An OAuth access token for the end-user.
- That token must include the
CREATE_VIRTUALCARDscope. - A virtual-card offer for the user (created via the existing offer API).
Basic-auth (client_id / client_secret) is not accepted on this query — it must be a user-context bearer token.
The query
query ProvisionCard($offerId: String!) {
getCardProvisioningUrl(input: { offerId: $offerId, platform: IOS }) {
url
expiresAt
}
}Input
| Field | Type | Required | Description |
|---|---|---|---|
offerId | String! (UUIDv4) | yes | The virtual-card offer to provision. Must be active, tokenization-eligible, and accessible to the user's account. |
platform | ProvisioningPlatform | no | Hint about where the user will open the URL. Defaults to IOS. See below — this does not lock the URL to one platform. |
ProvisioningPlatform
ProvisioningPlatformThe returned URL is platform-aware: iOS users automatically get the App Clip experience, Android users automatically get the Fluz app deep link. The platform value only affects what happens when the URL is opened somewhere other than an iOS or Android device (e.g. a desktop browser).
| Value | Desktop / unknown-device fallback |
|---|---|
IOS (default) | Apple App Clip launcher |
ANDROID | Fluz Android deep link |
OTHER | Fluz web app |
Pick the value that best matches where you expect the URL to be opened. If in doubt, leave it as IOS.
Output
| Field | Type | Description |
|---|---|---|
url | String! | The short URL to deliver to the user. Single-use. |
expiresAt | String! | ISO-8601 timestamp. The URL stops working at this time (≈ 5 minutes after creation). |
Example
Request
curl -X POST https://api.fluz.app/api/v1/graphql \
-H "Authorization: Bearer <user_access_token>" \
-H "Content-Type: application/json" \
-d '{
"query": "query($id:String!){ getCardProvisioningUrl(input:{ offerId:$id, platform:IOS }){ url expiresAt } }",
"variables": { "id": "11111111-2222-3333-4444-555555555555" }
}'Response
{
"data": {
"getCardProvisioningUrl": {
"url": "https://fluz.app.link/abc123XYZ",
"expiresAt": "2026-05-26T17:42:11.000Z"
}
}
}How to deliver the URL
The URL is opaque and contains no sensitive data — it's safe to surface in QR codes, SMS, email, or in-app UI. Common patterns:
- QR code on a screen — generate a QR from
urland show it; the user scans with their phone camera. - SMS / email — send the link directly to the user's phone or inbox.
- In-app deep link — wire a button in your mobile app that opens
url.
Whatever the delivery channel, the user must open the URL on a mobile device — that's where the wallet provisioning happens.
Lifetime & re-issuing
- Each call returns a fresh, single-use URL.
- The URL is valid until
expiresAt(≈ 5 minutes). - If a user doesn't act in time, simply call
getCardProvisioningUrlagain to mint a new one. There is no separate "refresh" endpoint.
Error handling
All errors come back in the standard Fluz GraphQL error shape, with the error name in extensions.code.
| Code | What it means | What to do |
|---|---|---|
Arguments.INVALID | offerId isn't a valid UUID. | Validate input on your side. |
VirtualCard.OFFER_NOT_FOUND | Offer doesn't exist or is inactive. | Confirm the offer id; create a new offer if needed. |
VirtualCard.OFFER_NOT_ACCESSIBLE | The user's account isn't eligible for this offer (campaign access). | Surface a generic "not available" message. |
VirtualCard.OFFER_NOT_AVAILABLE | The offer has already been redeemed into a card that is no longer active. | Don't retry with the same offer; create a new one. |
VirtualCard.OFFER_NOT_TOKENIZATION_ELIGIBLE | The offer's card program doesn't support wallet provisioning. | Don't offer "Add to wallet" for this card program. |
Auth.USER_REVOKED_DEVELOPER_ACCESS | The user has revoked OAuth access to your app. | Send the user back through OAuth consent. |
Auth.INVALID_SCOPE | The token doesn't have CREATE_VIRTUALCARD. | Request the scope during OAuth. |
Generic.EXTERNAL_SERVICE_ERROR | Transient downstream issue. | Retry — the call is safe to repeat. |
FAQ
Q: Does the URL contain the card number? No. The URL carries a short-lived opaque lookup id only. Credentials are exchanged securely on the device once the user opens the link.
Q: Can the same URL be reused? No. It's single-use. Generate a new one each time you need to surface the flow.
Q: What if the user is on desktop? They'll land on the fallback you selected with platform (App Clip launcher, Android deep link, or Fluz web app). For the wallet provisioning itself to work, they need to end up opening the URL on a mobile device.
Q: Do I need to do anything different for iOS vs. Android? No. The same URL works for both — Fluz routes per-device automatically.
Updated 1 day ago
