2025, Nov 04 05:00

How to resolve Telethon RPCError 400: PAYMENT_REQUIRED when sending Telegram Stars gifts (invoice-based flow)

Facing RPCError 400: PAYMENT_REQUIRED in Telethon when using TransferStarGiftRequest? Use invoice flow with InputInvoiceStarGiftTransfer & SendStarsFormRequest

Sending a Telegram Stars gift with Telethon can unexpectedly fail with RPCError 400: PAYMENT_REQUIRED, even when the account has enough stars and the item is not under a trade ban. If a previously working function started returning this error, the underlying flow you call is the key detail to review.

Reproducing the issue

The function below attempts to transfer a saved Stars gift directly via TransferStarGiftRequest. The recipient is obtained through get_input_entity, and the gift payload contains its msg_id.

async def push_gift_async(tg, recipient_input, present_data):
    try:
        result = await tg(TransferStarGiftRequest(
            stargift=InputSavedStarGiftUser(msg_id=present_data['msg_id']),
            to_id=recipient_input
        ))
        return True
    except Exception as exc:
        err_text = str(exc)
        print(f"Gift sending failed: {err_text}")
        return False, err_text

# elsewhere
# recipient_input = await tg.get_input_entity(user_id)
# present_data example: {"msg_id": 904312, "title": "Desk Calendar", "slug": "DeskCalendar-29932"}

In this setup, the call can throw the following error:

RPCError 400: PAYMENT_REQUIRED (caused by TransferStarGiftRequest)

What is actually going on

The transfer must follow the invoice-driven payment flow. Instead of invoking TransferStarGiftRequest directly, the server expects you to create an invoice for the transfer, retrieve a payment form, and then submit it. Without that sequence, you can run into PAYMENT_REQUIRED.

Fix: use the invoice and payment form flow

The working approach is to create an InputInvoiceStarGiftTransfer, fetch the form with GetPaymentFormRequest, and confirm it via SendStarsFormRequest. The msg_id still identifies the saved gift, and the recipient is provided as an InputUser with id and access_hash.

from telethon.tl.functions.payments import GetPaymentFormRequest, SendStarsFormRequest
from telethon.tl.types import InputUser, InputInvoiceStarGiftTransfer, InputSavedStarGiftUser

async def transfer_gift_via_invoice(tg, recipient_input, present_data):
    bill_payload = InputInvoiceStarGiftTransfer(
        stargift=InputSavedStarGiftUser(msg_id=present_data['msg_id']),
        to_id=InputUser(
            user_id=recipient_input.user_id,
            access_hash=recipient_input.access_hash
        )
    )

    pay_form = await tg(GetPaymentFormRequest(invoice=bill_payload))

    await tg(SendStarsFormRequest(
        form_id=pay_form.form_id,
        invoice=bill_payload
    ))

    return True

This sequence matches the expected payments flow for transferring a saved Stars gift.

Why this matters

Working with Telegram Stars means respecting the payments workflow the API enforces. Calling a low-level transfer without the invoice can lead to PAYMENT_REQUIRED, even if the balance looks fine. Using the invoice and form ensures the transfer aligns with what the server validates during the payment step.

Takeaways

If you hit RPCError 400: PAYMENT_REQUIRED on TransferStarGiftRequest, switch to the invoice path. Build InputInvoiceStarGiftTransfer for the selected saved gift’s msg_id and the exact recipient, fetch the form with GetPaymentFormRequest, then finalize via SendStarsFormRequest. This minimal change keeps your gift transfers stable and consistent with the current API flow.