Powered by Coinflow
Payments · Documentation
Operational

Apple Pay Push to Card

Merchants paying out users from a Coinflow wallet can save a user’s Apple Pay card as a reusable payout destination, rather than passing the raw Apple Pay token on every disbursement. This mirrors the way a debit card is linked with POST /withdraw/debit-card: the Apple Pay token is collected once, and the returned account token is used for every subsequent payout.

Step 1: Create a session key for the user

Each user is identified to Coinflow by an ID you choose (for example, your internal user ID). Create a session key for that user by calling Get Session Key with your API key and the user’s ID in the x-coinflow-auth-user-id header.

Request

GET https://api-sandbox.coinflow.cash/api/auth/session-key

curl https://api-sandbox.coinflow.cash/api/auth/session-key \
     -H "x-coinflow-auth-user-id: <apiKey>" \
     -H "Content-Type: application/json"
import requests

url = "https://api-sandbox.coinflow.cash/api/auth/session-key"

payload = {}
headers = {
    "x-coinflow-auth-user-id": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.get(url, json=payload, headers=headers)

print(response.json())
const url = 'https://api-sandbox.coinflow.cash/api/auth/session-key';
const options = {
  method: 'GET',
  headers: {'x-coinflow-auth-user-id': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-sandbox.coinflow.cash/api/auth/session-key"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("GET", url, payload)

	req.Header.Add("x-coinflow-auth-user-id", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
require 'uri'
require 'net/http'

url = URI("https://api-sandbox.coinflow.cash/api/auth/session-key")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["x-coinflow-auth-user-id"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api-sandbox.coinflow.cash/api/auth/session-key")
  .header("x-coinflow-auth-user-id", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api-sandbox.coinflow.cash/api/auth/session-key', [
  'body' => '{}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-coinflow-auth-user-id' => '<apiKey>',
  ],
]);

echo $response->getBody();
using RestSharp;

var client = new RestClient("https://api-sandbox.coinflow.cash/api/auth/session-key");
var request = new RestRequest(Method.GET);
request.AddHeader("x-coinflow-auth-user-id", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
import Foundation

let headers = [
  "x-coinflow-auth-user-id": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-sandbox.coinflow.cash/api/auth/session-key")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Response (200)

{
  "key": "a1b2c3d4e5f67890abcdef1234567890"
}

Step 2: Verify the user (KYC)

An Apple Pay card can only be linked to an approved withdrawer, so the user must complete identity verification first. The simplest path is Coinflow’s hosted Bank Authentication UI, opened with the session key from Step 1. You can also run KYC entirely through the API — see the merchant payout guide for the API-driven verification options.

Collect an Apple Pay token object from the Apple Pay SDK’s disbursement flow — see the Apple Pay Payouts API Implementation recipe — then link the card server-to-server by calling POST /withdraw/apple-pay. Pass the full ApplePayResponseObject — the token plus the billingContact — as applePayPayment, authenticating with your API key and the same x-coinflow-auth-user-id header used in Step 1.

Coinflow decrypts the token, tokenizes the underlying card, and runs a BIN check to confirm the card is a debit card that is eligible for push-to-card. Non-debit cards are rejected. On success the endpoint returns the account token for the saved Apple Pay card.

Request

POST https://api-sandbox.coinflow.cash/api/withdraw/apple-pay

curl -X POST https://api-sandbox.coinflow.cash/api/withdraw/apple-pay \
     -H "x-coinflow-auth-user-id: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "applePayPayment": {
    "token": {
      "transactionIdentifier": "string",
      "paymentMethod": {
        "network": "Visa",
        "displayName": "string"
      },
      "paymentData": {
        "version": "string",
        "header": {
          "transactionId": "string",
          "ephemeralPublicKey": "string",
          "publicKeyHash": "string"
        },
        "signature": "string",
        "data": "string"
      }
    },
    "billingContact": {
      "givenName": "string",
      "familyName": "string",
      "addressLines": [
        "string"
      ],
      "locality": "string"
    }
  }
}'
import requests

url = "https://api-sandbox.coinflow.cash/api/withdraw/apple-pay"

payload = { "applePayPayment": {
        "token": {
            "transactionIdentifier": "string",
            "paymentMethod": {
                "network": "Visa",
                "displayName": "string"
            },
            "paymentData": {
                "version": "string",
                "header": {
                    "transactionId": "string",
                    "ephemeralPublicKey": "string",
                    "publicKeyHash": "string"
                },
                "signature": "string",
                "data": "string"
            }
        },
        "billingContact": {
            "givenName": "string",
            "familyName": "string",
            "addressLines": ["string"],
            "locality": "string"
        }
    } }
headers = {
    "x-coinflow-auth-user-id": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
const url = 'https://api-sandbox.coinflow.cash/api/withdraw/apple-pay';
const options = {
  method: 'POST',
  headers: {'x-coinflow-auth-user-id': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"applePayPayment":{"token":{"transactionIdentifier":"string","paymentMethod":{"network":"Visa","displayName":"string"},"paymentData":{"version":"string","header":{"transactionId":"string","ephemeralPublicKey":"string","publicKeyHash":"string"},"signature":"string","data":"string"}},"billingContact":{"givenName":"string","familyName":"string","addressLines":["string"],"locality":"string"}}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-sandbox.coinflow.cash/api/withdraw/apple-pay"

	payload := strings.NewReader("{\n  \"applePayPayment\": {\n    \"token\": {\n      \"transactionIdentifier\": \"string\",\n      \"paymentMethod\": {\n        \"network\": \"Visa\",\n        \"displayName\": \"string\"\n      },\n      \"paymentData\": {\n        \"version\": \"string\",\n        \"header\": {\n          \"transactionId\": \"string\",\n          \"ephemeralPublicKey\": \"string\",\n          \"publicKeyHash\": \"string\"\n        },\n        \"signature\": \"string\",\n        \"data\": \"string\"\n      }\n    },\n    \"billingContact\": {\n      \"givenName\": \"string\",\n      \"familyName\": \"string\",\n      \"addressLines\": [\n        \"string\"\n      ],\n      \"locality\": \"string\"\n    }\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-coinflow-auth-user-id", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
require 'uri'
require 'net/http'

url = URI("https://api-sandbox.coinflow.cash/api/withdraw/apple-pay")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["x-coinflow-auth-user-id"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"applePayPayment\": {\n    \"token\": {\n      \"transactionIdentifier\": \"string\",\n      \"paymentMethod\": {\n        \"network\": \"Visa\",\n        \"displayName\": \"string\"\n      },\n      \"paymentData\": {\n        \"version\": \"string\",\n        \"header\": {\n          \"transactionId\": \"string\",\n          \"ephemeralPublicKey\": \"string\",\n          \"publicKeyHash\": \"string\"\n        },\n        \"signature\": \"string\",\n        \"data\": \"string\"\n      }\n    },\n    \"billingContact\": {\n      \"givenName\": \"string\",\n      \"familyName\": \"string\",\n      \"addressLines\": [\n        \"string\"\n      ],\n      \"locality\": \"string\"\n    }\n  }\n}"

response = http.request(request)
puts response.read_body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api-sandbox.coinflow.cash/api/withdraw/apple-pay")
  .header("x-coinflow-auth-user-id", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"applePayPayment\": {\n    \"token\": {\n      \"transactionIdentifier\": \"string\",\n      \"paymentMethod\": {\n        \"network\": \"Visa\",\n        \"displayName\": \"string\"\n      },\n      \"paymentData\": {\n        \"version\": \"string\",\n        \"header\": {\n          \"transactionId\": \"string\",\n          \"ephemeralPublicKey\": \"string\",\n          \"publicKeyHash\": \"string\"\n        },\n        \"signature\": \"string\",\n        \"data\": \"string\"\n      }\n    },\n    \"billingContact\": {\n      \"givenName\": \"string\",\n      \"familyName\": \"string\",\n      \"addressLines\": [\n        \"string\"\n      ],\n      \"locality\": \"string\"\n    }\n  }\n}")
  .asString();
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api-sandbox.coinflow.cash/api/withdraw/apple-pay', [
  'body' => '{
  "applePayPayment": {
    "token": {
      "transactionIdentifier": "string",
      "paymentMethod": {
        "network": "Visa",
        "displayName": "string"
      },
      "paymentData": {
        "version": "string",
        "header": {
          "transactionId": "string",
          "ephemeralPublicKey": "string",
          "publicKeyHash": "string"
        },
        "signature": "string",
        "data": "string"
      }
    },
    "billingContact": {
      "givenName": "string",
      "familyName": "string",
      "addressLines": [
        "string"
      ],
      "locality": "string"
    }
  }
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-coinflow-auth-user-id' => '<apiKey>',
  ],
]);

echo $response->getBody();
using RestSharp;

var client = new RestClient("https://api-sandbox.coinflow.cash/api/withdraw/apple-pay");
var request = new RestRequest(Method.POST);
request.AddHeader("x-coinflow-auth-user-id", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"applePayPayment\": {\n    \"token\": {\n      \"transactionIdentifier\": \"string\",\n      \"paymentMethod\": {\n        \"network\": \"Visa\",\n        \"displayName\": \"string\"\n      },\n      \"paymentData\": {\n        \"version\": \"string\",\n        \"header\": {\n          \"transactionId\": \"string\",\n          \"ephemeralPublicKey\": \"string\",\n          \"publicKeyHash\": \"string\"\n        },\n        \"signature\": \"string\",\n        \"data\": \"string\"\n      }\n    },\n    \"billingContact\": {\n      \"givenName\": \"string\",\n      \"familyName\": \"string\",\n      \"addressLines\": [\n        \"string\"\n      ],\n      \"locality\": \"string\"\n    }\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
import Foundation

let headers = [
  "x-coinflow-auth-user-id": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["applePayPayment": [
    "token": [
      "transactionIdentifier": "string",
      "paymentMethod": [
        "network": "Visa",
        "displayName": "string"
      ],
      "paymentData": [
        "version": "string",
        "header": [
          "transactionId": "string",
          "ephemeralPublicKey": "string",
          "publicKeyHash": "string"
        ],
        "signature": "string",
        "data": "string"
      ]
    ],
    "billingContact": [
      "givenName": "string",
      "familyName": "string",
      "addressLines": ["string"],
      "locality": "string"
    ]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-sandbox.coinflow.cash/api/withdraw/apple-pay")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Response (200)

"string"

Step 4: Initiate the delegated payout

Push funds to the linked card by calling POST /merchant/withdraws/payout/delegated with the user’s ID as userId, the token from Step 3 as account, and a speed of card — exactly as you would for a saved debit card. The payout is funded from your merchant settlement wallet. (The same account token also works on POST /merchant/withdraws/payout for user-initiated payouts.)

Request

POST https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated

curl -X POST https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated \
     -H "Authorization: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "speed": "same_day",
  "account": "card_4f3a2b1c9d8e7f6a",
  "userId": "user_1234567890abcdef",
  "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "amount": {
    "cents": 25000
  }
}'
import requests

url = "https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated"

payload = {
    "speed": "same_day",
    "account": "card_4f3a2b1c9d8e7f6a",
    "userId": "user_1234567890abcdef",
    "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "amount": { "cents": 25000 }
}
headers = {
    "Authorization": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
const url = 'https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated';
const options = {
  method: 'POST',
  headers: {Authorization: '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"speed":"same_day","account":"card_4f3a2b1c9d8e7f6a","userId":"user_1234567890abcdef","idempotencyKey":"3fa85f64-5717-4562-b3fc-2c963f66afa6","amount":{"cents":25000}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated"

	payload := strings.NewReader("{\n  \"speed\": \"same_day\",\n  \"account\": \"card_4f3a2b1c9d8e7f6a\",\n  \"userId\": \"user_1234567890abcdef\",\n  \"idempotencyKey\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"amount\": {\n    \"cents\": 25000\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
require 'uri'
require 'net/http'

url = URI("https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"speed\": \"same_day\",\n  \"account\": \"card_4f3a2b1c9d8e7f6a\",\n  \"userId\": \"user_1234567890abcdef\",\n  \"idempotencyKey\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"amount\": {\n    \"cents\": 25000\n  }\n}"

response = http.request(request)
puts response.read_body
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated")
  .header("Authorization", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"speed\": \"same_day\",\n  \"account\": \"card_4f3a2b1c9d8e7f6a\",\n  \"userId\": \"user_1234567890abcdef\",\n  \"idempotencyKey\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"amount\": {\n    \"cents\": 25000\n  }\n}")
  .asString();
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated', [
  'body' => '{
  "speed": "same_day",
  "account": "card_4f3a2b1c9d8e7f6a",
  "userId": "user_1234567890abcdef",
  "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "amount": {
    "cents": 25000
  }
}',
  'headers' => [
    'Authorization' => '<apiKey>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
using RestSharp;

var client = new RestClient("https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"speed\": \"same_day\",\n  \"account\": \"card_4f3a2b1c9d8e7f6a\",\n  \"userId\": \"user_1234567890abcdef\",\n  \"idempotencyKey\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"amount\": {\n    \"cents\": 25000\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
import Foundation

let headers = [
  "Authorization": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "speed": "same_day",
  "account": "card_4f3a2b1c9d8e7f6a",
  "userId": "user_1234567890abcdef",
  "idempotencyKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "amount": ["cents": 25000]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-sandbox.coinflow.cash/api/merchant/withdraws/payout/delegated")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

Response (200)

{
  "effectiveSpeed": "same_day",
  "signature": "3045022100dff9a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789ab02207c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e"
}

Pass a unique idempotencyKey on every payout so a retried request can never double-pay, and read effectiveSpeed from the response to confirm how the payout was actually processed.

Step 5: Monitor the payout

Track payout status through Withdraw Webhooks, correlated by the signature returned in Step 4. The linked card stays saved on the withdrawer and can be reused for future payouts without re-collecting an Apple Pay token.