Payments API

You can accept payments from Telegram users via Telegram Bots.

Note: This article is intended for MTProto API developers. If you're looking for a general overview of Telegram Payments, check out the Telegram blog and the bot API payment manual.

Introducing Payments

Telegram bots can accept payments for goods and services from users. For more info on how payments work, check out the Telegram Blog and the bot API payment manual.

This page will elaborate on the actions required to work with payments using the MTProto API.

A simplified version of the process is available only for bots using the bot API.

The first step for bots is enable payments as described here ».

Then, we work with payments as follows.

1. Create Invoice

inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;

labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;

invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;

inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;

---functions---

messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;

The user contacts the bot and requests to purchase something. The bot forms an inputMediaInvoice with an invoice constructor with a description of the goods or service, amount to be paid, as well as requested shipping info. The provider parameter of the inputMediaInvoice constructor is where you put the token value that you've obtained earlier via Botfather. It is possible for one merchant bot to use several different tokens for different users or different goods and services.

Use the messages.sendMedia method to send the invoice. You can also attach an inline keyboard to the message using the reply_markup field: if provided, the first button must be a keyboardButtonBuy button. Otherwise, an inline keyboard will be generated automatically, with a Pay 'total price' keyboardButtonBuy as only button.

An invoice message with a pay button can only be sent to a private chat with the user. Groups and channels are not supported.

2. Order information

2.1 Invoice message

keyboardButtonBuy#afd93fbb text:string = KeyboardButton;

keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;

webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;

messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;

message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;

updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;

The user receives an updateNewMessage constructor from the bot, containing a messageMediaInvoice constructor with basic info about the product.

The message will also have a replyInlineMarkup keyboard attached to it. The the first button of the keyboard will always be a keyboardButtonBuy button.

2.2 Getting invoice info about the product

invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;

paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;

paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;

payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

---functions---

payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;

If the user clicks on the keyboardButtonBuy button, the client proceeds to call payments.getPaymentForm with the message ID of the invoice preview message to get the payment form.

The returned form will contain fields that should be passed to the payment provider along with the full invoice. The payment form also contains info about previously saved payment credentials and order information (name, phone number, email, shipping address & so on).

The full invoice contains info about the information required for the order, the price and the currency, and whether this is a test order.

2.3 Verifying information

invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;

postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress;

paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;

payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;

---functions---

payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;

If any data at all is requested by the invoice (name_requested, phone_requested, email_requested, shipping_address_requested), the user must call payments.validateRequestedInfo, providing the required data (as usual, msg_id is the ID of the invoice message). The user can choose to save order information for future use by setting the save flag. Data can be autofilled as described in autofill.

If no errors are found in the submitted info, the response of the method will contain an id flag, to be used later to complete the payment.

If the flexible flag of the invoice is set, calling the payments.validateRequestedInfo method will send a shipping query update to the bot, to which the bot will reply with the available shipping options for the specified address as described here ». The return value in this case will also contain a shipping_options field with the available shipping options.

If any errors are found in the submmitted data, a service notification will be sent to the user, with a description of the error from the bot.

2.3.1 Autofill

payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;

---functions---

payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;

The requested fields can be autofilled with the info provided in the saved_info field of the payment form, or with the info fetched manually using payments.getSavedInfo.

Saved order information can also be cleared using payments.clearSavedInfo.

2.4 Select delivery option

labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;

shippingOption#b6213cdf id:string title:string prices:Vector<LabeledPrice> = ShippingOption;

updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;

---functions---

messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;

If a shipping address was requested and the bot included the parameter flexible, when the user validates order information the Telegram API will send an updateBotShippingQuery to the bot. The bot must respond using messages.setBotShippingResults either with a list of possible delivery options and the relevant delivery prices, or with an error (for example, if delivery to the specified address is not possible).

The returned shipping options or the shipping error will be returned to the user while validating order information.

3. Payment

inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;


payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

3.1 Web payment

inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;

payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

Typically, payment takes place by opening the url in the specified payment form, which leads to a payment form on the website of the payment gateway. Once the user finishes entering their payment credentials, a payment_form_submit web event is generated by the payment gateway, containing data and title JSON fields.

The title is used by the client app to represent the payment credentials (typically a censored version of credit card information). The data is used to generate an inputPaymentCredentials constructor. Eventually, you can set the save flag to save the credit card info for future use, only if 2FA is enabled.

Telegram does not have access to your card information. Credit card details will be handled only by the payment system.

3.2 Native payment

inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;

payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

Most telegram apps support working natively with the native APIs of some payment providers, without opening the website of the payment and receiving a JS event.

This is done using the JSON native_params parameters field of the payments.paymentForm constructor, which contains an object, which can contain one or more of the following fields:

  • publishable_key: Stripe API publishable key
  • apple_pay_merchant_id: Apple Pay merchant ID
  • android_pay_public_key: Android Pay public key
  • android_pay_bgcolor: Android Pay form background color
  • android_pay_inverse: Whether to use the dark theme in the Android Pay form
  • need_country: True, if the user country must be provided,
  • need_zip: True, if the user ZIP/postal code must be provided,
  • need_cardholder_name: True, if the cardholder name must be provided

The payment gateway to use is decided based on the value of the native_provider field.

3.2.1 Stripe
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;

payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

If the native_provider field is set and equal to stripe, the client can make use of the native Stripe token APIs with the publishable_key from the native_params to add a payment method to Stripe, and then use the token type and id to generate a JSON object:

{"type":"token.type", "id":"token.id"}"

The generated JSON object can then be passed to the data field of the inputPaymentCredentials. Eventually, you can set the save flag to save the credit card info for future use, only if 2FA is enabled.

Telegram does not have access to your card information. Credit card details will be handled only by the payment system.

Example implementation: Telegram for Android.

3.3 Apple pay

inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;

On iOS devices, Apple Pay can be used to generate payment data, which is then sent using the inputPaymentCredentialsApplePay constructor.

Example implementation: Telegram for iOS.

3.4 Android pay

inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;

On Android devices, Google Pay can be used to generate payment data, which is then sent using the inputPaymentCredentialsAndroidPay constructor.

Example implementation: Telegram for Android.

3.5 Using saved payment credentials

inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;

paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;

payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;

account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;

---functions---

account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;

To reuse saved payment methods, the saved_credentials field of the payment form is used. The title of the paymentSavedCredentialsCard can be used to preview a censored version of credit card info. The id field is provided by the payment provider directly to the Telegram servers when saving the payment method, and identifies the payment method. Full credit card info is not saved on Telegram Servers, and cannot be fetched by the user.

In order to use the saved payment method, 2FA must be enabled: the user must verify their identity by entering their 2FA password, which is then used as described in the SRP docs to generate SRP parameters which must be passed to account.getTmpPassword.

The generated temporary password can then be used to make payments using the saved credentials using the inputPaymentCredentialsSaved constructor.

Example implementation: Telegram for Android.

4. Pre-Checkout

inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;

payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;

---functions---

payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;

After verifying order information, the final step for the client is to call payments.sendPaymentForm, with the following parameters:

  • The msg_id is set to the ID of the invoice message
  • requested_info_id is set to the id of the verified order information, if it was requested
  • shipping_option_id is set to the selected delivery option, if shipping was requested.
  • credentials are the payment credentials generated by the payment provider, required to complete the order.

Payment method info can also be saved to the Telegram Servers and reused, by setting the save flag of inputPaymentCredentials when sending the form. This is only possible on accounts with 2FA enabled.

The bot then replies to the received precheckout query, finally the user proceeds to checkout.

Please note that if the result of the method is a payments.paymentVerificationNeeded, before proceeding to checkout the payment provider requires the user to verify his identity by opening the provided url and following instructions. Once the user finishes working with the webpage, the client can proceed to checkout.

Eventual errors are returned in the form of RPC errors, with the description of the error by the bot contained in service updates.

4.1 Receiving pre-checkout query

paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;

updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;

---functions---

messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;

The user enters their payment information as described above and presses the final pay button. At this moment the Telegram API sends an updateBotPrecheckoutQuery constructor that contains all the available information about the order to the bot. The bot must reply using messages.setBotPrecheckoutResults within 10 seconds after receiving this update or the transaction is canceled.

The bot may return an error if it can‘t process the order for any reason. We highly recommend specifying a reason for failure to complete the order in human readable form (e.g. "Sorry, we’re all out of rubber ducks! Would you be interested in a steel bear instead?"). Telegram will display this reason to the user.

5. Checkout

keyboardButtonBuy#afd93fbb text:string = KeyboardButton;

keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;

messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;

message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;

updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;

payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;

---functions---

payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;

In case the bot confirms the order, Telegram requests the payment provider to complete the transaction. If the payment information was entered correctly and the payment goes through, the Telegram API will modify the invoice message and send a service message as described below. Once your bot receives this message, it should proceed with delivering the goods or services purchased by the user.

If all is OK, the user receives a payments.paymentResult in reply to the payments.sendPaymentForm query, containing info about the updated invoice message in the form of an updateEditMessage.

The invoice message will be updated as follows: the attached messageMediaInvoice will now have a receipt_msg_id field. Clients should treat invoice messages with a receipt_msg_id field as receipt messages, locally modifying the label of the keyboardButtonBuy button to a localized version of the word Receipt. From this point, clicking on the Receipt button should trigger a call to payments.getPaymentReceipt, providing the receipt_msg_id to the msg_id field, which will return info about the transaction.

The payment will also generate one service message of type messageActionPaymentSent or messageActionPaymentSentMe, replying to the invoice. For bots, the service message will be of type messageActionPaymentSentMe, for users it will be a messageActionPaymentSent.

messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;