Getting Started with Pocket Bitcoin's API

Pocket Bitcoin is a fantastic place to buy Bitcoin if you live in Europe. In addition to being a great place to buy Bitcoin, they also offer API access to developers who want to integrate their products with Pocket. This unlocks a new revenue stream for you through their affiliate program and gives your users a smooth, KYC-light purchase experience within a product they're already using.

If you've never used Pocket before, I recommend trying a purchase through their site. The purchase flow through the API is similar to the UI flow and it's helpful to have a foundation of how their product works.

Access

Pocket requires developers authenticate their API calls with a client ID so your first step is to get a client ID. Head over to Pocket's Contact page and explain why you want API access.

The team will reply back soon with your client ID or additional questions about your request. Assuming they approve your request, they will send credentials and configure a temporary webhooks receiver. I'll explain more about webhooks later in this guide.

Before we move on, make sure your client ID is working with your first request to their API. Their API requires an 'Authorization' header with the value containing the prefix 'client_id' followed by your client ID.

curl "https://api.pocketbitcoin.com" \
  -H "Authorization: client_id YOUR_CLIENT_ID"

I recommend adding your client ID to an environment variable which will allow you to copy and paste the curl commands in this guide without having to manually add your client ID each time. Run the following in your terminal, adding your actual client ID where YOUR_CLIENT_ID is.

export POCKET_CLIENT_ID=YOUR_CLIENT_ID

Test your environment variable.

echo $POCKET_CLIENT_ID

If your terminal responds with your client ID, you can move on to testing an API call.

curl "https://api.pocketbitcoin.com" \
    -H "Authorization: client_id "$POCKET_CLIENT_ID""

If you get a 401 Unauthorized error, double check your environment variable was set correctly. Carry on if you can't figure it out and add the client ID manually.

Resources

The API has three key resources to understand.

  1. Challenge: A randomly generated token used in a signed message confirming ownership over a Bitcoin address. Challenges can be only used once and expire after 24 hours. These can be created and retrieved through the API.
  2. Order: A set of payment and payout instructions to be used in a Bitcoin and fiat exchange. Orders can be used indefinitely for multiple purchases. These can be created, updated (with limitations), and retrieved through the API.
  3. Exchange: A swap event of Bitcoin and fiat currency. Exchanges are created automatically when a user follows the payment instructions in an Order. Thus, these can only be retrieved through the API.  

High Level Flow

Hopefully you had a chance to try a purchase on their site. Here's a high level overview of how purchases work through the API. Some details are left out to avoid too much complexity too soon.

  1. App creates a challenge token with Pocket Bitcoin.
  2. App passes challenge token to User.
  3. User provides the following to App:
    • Signed message with challenge token
    • User's Bitcoin address
    • User's IBAN
    • User's currency; EUR or CHF
  4. App sends the above details to Pocket Bitcoin to create an order.
  5. Pocket Bitcoin responds with an order containing the following:
    • Pocket Bitcoin's IBAN
    • Payment reference number
  6. App provides order details to User and requests them to make payment to Pocket Bitcoin.
  7. User sends payment to Pocket Bitcoin and includes the payment reference number in the payment description.
  8. Pocket Bitcoin creates an exchange once it sees the payment and begins sending status webhooks to App.
  9. Pocket Bitcoin sends Bitcoin to User at the end of the day.

Creating a Challenge

The first call we'll make is a POST to /v1/challenges to generate a random token. This POST request does not take any data in its request body. You can leave it out of your request if you want.

curl -X POST "https://api.pocketbitcoin.com/v1/challenges" \
    -H "Authorization: client_id "$POCKET_CLIENT_ID"" \
    -H "Content-Type: application/json" \
    --data-raw '{}'

We should receive the following response:

{
    "id": "d7a2907b-3921-4bdc-9922-67e0bafa8187",
    "token": "qf3GFbmi",
    "expires_on": "2022-12-07T15:31:53.259Z",
    "completed_on": null
}

Great. We have our token and now we can create a signed message.

Signing Message

Before we can create an order, we need to create a signed message using the token from the previous step. The message can say anything, but it must contain a valid token. Pocket's docs provide this sample message that will work for our purposes too: "I confirm my bitcoin wallet. [qf3GFbmi]"

For testing purposes, I recommend setting up a wallet in Sparrow or Electrum. These wallets make it simple to sign a message with a simple UI tool. See my guide on signing messages in Sparrow if you need help. My guide assumes you're using a hardware wallet as a signing device, but if you're just testing, you can create a hot wallet through Sparrow or Electrum to sign messages.

Navigate to the 'Sign/Verify Message' option under 'Tools' and paste the Bitcoin address you want to receive to and your messsage.

Hit the 'Sign' button and the 'Signature' field will be populated with the signature we'll need to create an order. Keep this window open. We'll need the address, message, and signature in the next step.

Creating an Order

In addition to the 3 items mentioned above, you'll also need to decide the following:

  1. A fee rate ranging 1.2% to 4.5% in float form (e.g. 0.015 for 1.5%). I've set the fee to 1.2% in the sample below.
  2. Currency you'll pay with; EUR or CHF only.
  3. IBAN for the bank account you'll pay Pocket with.

Replace the values in the sample below with yours. All fields you need to add are prefixed with YOUR_....

curl -X POST "https://api.pocketbitcoin.com/v1/orders" \
    -H "Authorization: client_id "$POCKET_CLIENT_ID"" \
    -H "Content-Type: application/json" \
    --data-raw '{
        "active": true,
        "fee_rate": 0.012,
        "payment_method": {
            "currency": "YOUR_CURRENCY",
            "debitor_iban": "YOUR_IBAN"
        },
        "payout_method": {
            "bitcoin_address": "YOUR_BITCOIN_ADDRESS",
            "message": "I confirm my bitcoin wallet. [YOUR_TOKEN]",
            "signature": "YOUR_SIGNATURE"
        }
    }'

If you become a Pocket affiliate partner, you would also include an affiliate_id field to track the customers you send their way.

A successful response will return 201 Created and a response body containing the details to make payment.

{
  "id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
  "active": true,
  "created_on": "2022-12-07T15:32:59.211Z",
  "fee_rate": 0.012,
  "payment_method": {
    "currency": "eur",
    "debitor_iban": "DE75512108001245126199",
    "creditor_reference": "RF18GW8K79",
    "creditor_iban": "CH9400778214768302002",
    "creditor_bank_name": "Luzerner Kantonalbank",
    "creditor_bank_street": "Pilatusstrasse 12",
    "creditor_bank_postal_code": "6002",
    "creditor_bank_town": "Luzern",
    "creditor_bank_country": "CH",
    "creditor_bank_bic": "LUKBCH2260A",
    "creditor_name": "Pocket App",
    "creditor_street": "Industriestrasse 33",
    "creditor_postal_code": "5242",
    "creditor_town": "Lupfig",
    "creditor_country": "CH",
    "swiss_qr_bill_payload": null
  },
  "payout_method": {
    "bitcoin_address": "bc1q5a7uxjq6z5l2wguzu77rzmzk6tyx24yvq5r4fj",
    "message": "I confirm my bitcoin wallet. [qf3GFbmi]",
    "signature": "IDoGQ1yyj257K5c/eroZcn1zWg1QULuqo/ALqjkRFEfZE8CA8ANp0+IZ2KpQz2PUhn+ryUy4JWI7J+VbYz/aXOM="
  }
}

You can look up this order at any time through the get order endpoint. You can also update the order's affiliate ID, fee rate, and active status through the patch endpoint. See Pocket's docs for examples of those.

Making Payment

Payment must be done from your banking app to Pocket's bank account provided in the order response body. You can theoretically send any amount but it's advisable to send a small amount first. For a large amount, Pocket suggests contacting them first.

It's crucial you include the payment reference number in your payment description to Pocket. This is found in the creditor_reference field. RF18GW8K79 in the sample response above. This field is used by Pocket to link your fiat payment to the order and send the payout to the right Bitcoin address.

Exchanges

Webhooks

Pocket uses webhooks to keep developers up to date on exchanges. Listening for webhooks is crucial to any integration because there is no other way to get updates on exchanges. There is an endpoint to fetch exchanges, but if you're not listening for exchange webhooks, you won't know the exchange ID needed to look it up. Once you have the ID, you can of course query the exchange endpoint. Hopefully Pocket will soon add an endpoint to search for exchanges with query params such as date ranges or by order ID.

Pocket will provision a temporary webhooks endpoint for you when you first get API credentials. See the webhooks.site link they sent you to watch for webhooks.

Contact the Pocket team once you have your own webhook endpoint configured to rotate where you receive notifications.

Pocket will send a signature header along with every webhook. If you're building a real production application, use this to verify the webhook really came from Pocket. More information on this is available here.

Executed Exchange

Pocket will send an executed exchange webhook once they receive your fiat payment. See the rate field for the Bitcoin price you got.

{
  "id": "a2d75998-5ee5-4501-a4cf-4e3788732b7a",
  "order_id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
  "fee_rate": 0.012,
  "pair": "btc/eur",
  "type": "buy",
  "cost": 100.00,
  "executed_on": "2022-12-07T15:32:59.211Z",
  "amount": 0.00494000,
  "rate": 20000.00,
  "fee": 1.20,
  "action": null,
  "reason": null,
  "payout": null
}

Settled Exchange

Pocket will send a settled exchange webhook when they have paid out to your Bitcoin address. Exchanges settle at the end of the day in which Pocket received your fiat payment. This is usually around 10pm Central European Time.

The settled exchange webhook will be the same as above except the payout field will be populated with details on the payout transaction.

{
  "id": "a2d75998-5ee5-4501-a4cf-4e3788732b7a",
  "order_id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
  "fee_rate": 0.012,
  "pair": "btc/eur",
  "type": "buy",
  "cost": 100.00,
  "executed_on": "2022-12-07T15:32:59.211Z",
  "amount": 0.00494000,
  "rate": 20000.00,
  "fee": 1.20,
  "action": null,
  "reason": null,
  "payout": {
    "txid": "c01c7a0fd270aa3876119098c0e2d51687a795efab99787e214d8a93ab9f8342",
    "outpoint": 1,
    "bitcoin_address": "bc1q5a7uxjq6z5l2wguzu77rzmzk6tyx24yvq5r4fj",
    "derivation_path": null,
    "block_height": null,
    "confirmed": false,
    "fee": 0.00000190,
    "amount": 0.00049381
  }
}

Interrupted Exchange

An interrupted exchange means the exchange is blocked and you must take a particular action in order to unblock it. For example, if you try to buy a very large amount of Bitcoin, you may trigger a request for identification. See the action field.

{
  "event": "exchange.interrupted",
  "payload": {
    "exchange_id": "a2d75998-5ee5-4501-a4cf-4e3788732b7a",
    "order_id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
    "fee_rate": 0.012,
    "pair": "btc/eur",
    "type": "buy",
    "cost": 100.00,
    "executed_on": null,
    "amount": null,
    "rate": null,
    "fee": null,
    "action": {
      "code": "identification_required",
      "identification_id": "7bf967b5-3d34-4cd6-936c-6631811401d2"
    },
    "reason": null,
    "payout": null
  }
}

Refunded Exchange

This one is fairly simple. You'll get this notification if a fiat payment had to be refunded. There are several potential refund reasons. The example below shows a refund due to threshold_exceeded in the reason field. The full list is available in Pocket's documentation.

{
  "event": "exchange.refunded",
  "payload": {
    "exchange_id": "a2d75998-5ee5-4501-a4cf-4e3788732b7a",
    "order_id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
    "fee_rate": 0.012,
    "pair": "btc/eur",
    "type": "buy",
    "cost": 100.00,
    "executed_on": null,
    "amount": null,
    "rate": null,
    "fee": null,
    "action": null,
    "reason": {
      "code": "threshold_exceeded"
    },
    "payout": null
  }
}

Fetching an Exchange

As mentioned previously, you can retrieve exchanges through the API if you have the exchange's ID.

curl -X GET "https://api.pocketbitcoin.com/v1/exchanges/a2d75998-5ee5-4501-a4cf-4e3788732b7a" \
    -H "Authorization: client_id "$POCKET_CLIENT_ID""

The response won't explicitly tell you the status of the exchange. Based on what fields are populated, you can understand the state of it.

  • Executed if the following fields are null: action, reason, and payout.
  • Settled if payout is populated.
  • Interrupted if action is populated.
  • Refunded if reason is populated.
{
  "id": "a2d75998-5ee5-4501-a4cf-4e3788732b7a",
  "order_id": "64decab6-4129-4fe7-9f6e-1db68283f5ce",
  "fee_rate": 0.012,
  "pair": "btc/eur",
  "type": "buy",
  "cost": 100.00,
  "executed_on": "2022-12-07T15:32:59.211Z",
  "amount": 0.00494000,
  "rate": 20000.00,
  "fee": 1.20,
  "action": null,
  "reason": null,
  "payout": null
}

What Next?

That wraps up my guide on Pocket's API. Check out the following resources if you'd like to learn more or start playing around with their API.

  1. Pocket's API Docs
  2. Pocket's Postman Collection
  3. My OpenAPI Config for Pocket (use this to auto-generate an API client library in any language you want)
You've successfully subscribed to Alex Barron
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.