Ethereum API: ERC-20 Transfers With One REST Call
Send ETH and ERC-20 tokens with one REST call instead of web3.js and raw JSON-RPC. Wallets, transfers and deposit webhooks, no node to operate.
The Chaingateway Ethereum API sends ETH and ERC-20 tokens over one authenticated REST call and replaces the raw JSON-RPC sequence a manual integration needs: encoding the transfer against the contract ABI, estimating gas, managing the nonce and signing the transaction, all before error handling. Libraries like web3.js and ethers.js wrap those steps, but they still run inside your stack and still need a node endpoint behind them.
Chaingateway moves that work server-side. A webhook tells you when deposits arrive. There is no SDK to install and no node to run. The free 7-day trial starts without KYC.
Quickstart: three steps to a first transfer
Create an account and copy your API key. Register here — the trial starts without KYC, so this step takes about a minute — then copy the key from your dashboard into the Authorization: Bearer header of every request. Store it server-side; a key in frontend code is public.
Make the hello-world call. GET /api/account returns your account details and proves the key works.
Send a testnet transfer, then go live. Add X-Network: testnet, import a throwaway key via POST /api/v2/ethereum/addresses/import, fund it from a public faucet, and send the ERC-20 transfer shown below. Register a webhook so deposits flow back to you, then drop the testnet header — the identical code runs on mainnet.
REST instead of JSON-RPC and web3.js
JSON-RPC is the native protocol of every Ethereum node, and for some jobs (consensus tooling, custom indexing) you want that level of access. Our guide to interacting with nodes via JSON-RPC shows what that looks like in PHP, Python and JavaScript.
Counting the round trips makes the point. A token transfer over raw JSON-RPC touches at least four methods — eth_gasPrice, eth_estimateGas, eth_getTransactionCount and eth_sendRawTransaction — with ABI encoding and transaction signing between them.
For payments, the abstraction pays off. In Chaingateway's transaction request, gas limit, gas price and nonce are optional fields — leave them out and the API fills them in when it builds and broadcasts the transaction; pass them explicitly when you want control. Your side of the exchange is one HTTP request you can write in any language with a standard library.
Everything you need to build on Ethereum
Webhooks (IPN)
Real-time notifications for incoming transactions, sent as soon as a matching transfer settles on-chain. With a personal secret set in your profile, each notification carries an X-Signature header your server can verify. Failed deliveries are listed by the API and can be re-sent with one call.
Easy transactions
Send ETH and ERC-20 tokens without touching the fee market: gas limit, gas price and nonce are optional request fields the API fills in for you. You supply the recipient, the token and the amount.
Secure address handling
Address format validation on every request — malformed addresses fail with a 422 before anything is built — and a non-custodial architecture. Existing keys come in through POST /api/v2/ethereum/addresses/import.
Decoded queries
Transactions come back as readable JSON through GET /api/v2/ethereum/transactions/{txid}/decoded, in a format your application can read and store without extra parsing.
Ethereum endpoints at a glance
| Route | Method | What it does |
|---|---|---|
/api/account | GET | Account details; the standard key check |
/api/v2/ethereum/addresses/import | POST | Bring an existing private key under API management |
/api/v2/ethereum/transactions/erc20 | POST | Send an ERC-20 token transfer |
/api/v2/ethereum/webhooks/notifications | GET | List received deposit notifications |
Four routes cover the payment loop: prove the key, load a wallet, send tokens, audit what came in. Exact request and response schemas live in the API reference, and the same layout repeats on Polygon, Arbitrum and — with bep20 in place of erc20 — on BNB Smart Chain.
The core example: send an ERC-20 token
Step 1: import the address you want to send from.
curl -X POST https://app.chaingateway.io/api/v2/ethereum/addresses/import \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"address": "0xYourWallet...", "privatekey": "0x...", "password": "strong-wallet-password"}'curl -X POST https://app.chaingateway.io/api/v2/ethereum/transactions/erc20 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contractaddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"from": "0xYourWallet...",
"to": "0xRecipient...",
"amount": 25.50,
"password": "strong-wallet-password"
}'curl https://app.chaingateway.io/api/v2/ethereum/webhooks/notifications \
-H "Authorization: Bearer YOUR_API_KEY"That's the full transfer, gas fields included — create an account and send it on Sepolia first.
Coming from web3.js: the same transfer, twice
If you maintain a web3.js integration today, here is the honest comparison. An ERC-20 transfer through the library looks roughly like this:
// web3.js against your own RPC endpoint
const { Web3 } = require("web3");
const web3 = new Web3("https://your-rpc-endpoint");
const token = new web3.eth.Contract(ERC20_ABI, "0xdAC17F958D2ee523a2206206994597C13D831ec7");
const data = token.methods.transfer(recipient, amountInBaseUnits).encodeABI();
const tx = {
from: sender,
to: token.options.address,
data,
gas: await web3.eth.estimateGas({ from: sender, to: token.options.address, data }),
gasPrice: await web3.eth.getGasPrice(),
nonce: await web3.eth.getTransactionCount(sender),
};
const signed = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY);
await web3.eth.sendSignedTransaction(signed.rawTransaction);
Beyond what fits in the snippet, this code owns an ABI file, converts human amounts into base units by hand (get the decimals wrong and you send a millionth of the intended sum, or a million times it), and holds a raw private key in application memory. The REST version is the single POST above: amount as a decimal string, decimals handled server-side, key encrypted at rest behind a password.
The migration does not need a rewrite weekend. Both styles are plain calls, so run them side by side: route new payment flows through REST, leave custom contract interactions on web3.js, and retire the library wherever it no longer earns its complexity. Teams that used web3.js only for transfers and balance checks tend to remove the dependency entirely.
Who pays the gas — and how it works
Every Ethereum transaction burns gas, paid in ETH by the sending address, never the recipient. A wallet holding thousands of USDT but zero ETH cannot send a single token, because the ERC-20 contract has no way to cover its own execution cost. Receiving tokens costs the recipient nothing.
This detail trips up more ERC-20 integrations than any other. If your API-driven transfers come from a treasury wallet, that wallet needs an ETH balance next to its tokens, and topping it up belongs on the ops checklist beside certificate renewals.
How much gas costs
A plain ETH transfer costs exactly 21,000 gas — a protocol constant. An ERC-20 transfer runs contract code and costs a multiple of that, with the exact figure varying by token contract. The price per unit floats with demand: since the London upgrade of 2021, the fee splits into a base fee the network burns and a priority tip to the block proposer, and both rise under congestion. The practical consequence: the same USDT transfer costs cents on a quiet Sunday and considerably more during a popular mint.
What the API handles for you
Gas limit, gas price and the EIP-1559 caps (maxFeePerGas, maxPriorityFeePerGas) are optional request fields — omit them and the API fills them when it builds your transaction, or pin them per request when you want the control. Two things remain your responsibility: keeping ETH on sending wallets, and deciding where small transfers make economic sense — when mainnet fees approach the transfer amount, the same call on Polygon or Arbitrum is a path change away.
Receiving is free
A deposit address needs no ETH to accept tokens. Gas becomes your problem only when funds move out — including when you sweep customer deposits into a treasury wallet, which is itself an outbound transaction from each deposit address.
Block times and finality on Ethereum
Since the switch to proof of stake, Ethereum's timing is fixed rather than statistical. Blocks arrive in twelve-second slots, 32 slots form an epoch of 6.4 minutes, and a block is finalized after roughly two epochs — call it 13 minutes — once two-thirds of staked ETH has attested to it (protocol parameters as of mid-2026). Finalized means the network cannot revert the block without destroying a large share of all staked ETH, which puts it in a different category from Bitcoin's probabilistic settlement.
For payment logic the timeline reads like this: a transfer is typically included in a block within seconds to a minute; each further slot adds assurance; after about 13 minutes it is final in the strict sense. Most applications credit deposits well before finality — inclusion plus a handful of blocks covers everyday amounts — while exchanges commonly hold large withdrawals until finalization. The deposit webhook gives you the on-chain event; where you set the crediting bar is a policy line in your config, not ours.
Any token on Ethereum, including your own
USDT, USDC, DAI and the other established tokens work out of the box, with amounts in token units instead of raw base units. Launching your own ERC-20? Supply the contract address and the same endpoint sends it. No listing process, no waiting. Background on the standard itself is in our ERC-20 token guide.
Why developers choose Ethereum
- It is the most widely adopted smart contract platform, battle-tested since 2015.
- The DeFi ecosystem is the largest of any chain, with thousands of dApps to integrate against.
- Gas pricing is dynamic, based on network demand; you can leave the fee fields to the API or cap them per request with the EIP-1559 parameters.
- Scaling work continues, and Polygon and Arbitrum are available through the same API when mainnet fees bite.
One integration, four EVM chains
The ERC-20 route pattern repeats across EVM networks: /api/v2/polygon/transactions/erc20, /api/v2/arbitrum/transactions/erc20, and /api/v2/bsc/transactions/bep20 for BNB Smart Chain. Code written for Ethereum ports by editing the path. When mainnet gas gets too expensive for small transfers, moving them to Polygon is a one-line change. The full chain list is on the blockchain API overview.
Built for real use cases
The building blocks above cover most production patterns we see: checkout flows that accept USDT or USDC, exchanges that credit deposits and process withdrawals at scale, token projects distributing through airdrops or vesting schedules, and subscription businesses billing in stablecoins each month. All of them reduce to the same two calls: send a transaction, receive a webhook.
Two builds, end to end
Stablecoin checkout for a web shop
The customer picks "pay with USDT" and your backend assigns a deposit address for the order — one address per invoice, so attribution never depends on matching amounts. Display the address with the amount, then wait for the webhook. When the notification arrives, flip the order to "payment detected" so the buyer sees a reaction quickly; credit it once the transaction has the depth your policy requires (the finality section above gives you the numbers). One edge case belongs in code from day one: wallets that subtract fees from the entered amount produce slight underpayments, and your tolerance for those should be a config value, not a support ticket.
Withdrawals for a trading platform
Users request payouts; your job is to send many ERC-20 transfers reliably. Queue each withdrawal in your database with a state column, then work through the queue with POST /api/v2/ethereum/transactions/erc20 — one request per payout, recording the response before moving to the next. The rule that keeps auditors happy: a timed-out HTTP call is not a failed transaction. Verify against your records and GET /api/v2/ethereum/transactions, which lists every transaction created through the API, before re-sending anything, or you will pay someone twice. Monitor the treasury wallet's ETH balance as well, since every outbound transfer burns gas, and alert well before it runs dry rather than when the queue stalls.
Handling errors
Two layers of failure exist here: HTTP errors from the API, and on-chain conditions your logic has to absorb.
The HTTP side follows convention. A 401 means the key is missing or invalid — a config problem, not a retry candidate. Other 4xx responses say the request is wrong: a malformed address, an unknown contract, a missing field. Log the response body and fix the caller. Retries with backoff belong to 5xx responses and network timeouts only.
The chain side is where payments code earns its salary. A transfer from a wallet without enough ETH for gas fails even though the token balance is ample — monitor gas balances proactively instead of discovering them empty in an error message. Congestion can delay inclusion; that is a delay, not a failure, and your UI should tell the two apart. And the rule from the withdrawal walkthrough bears repeating, because it guards against the most expensive mistake in this domain: never re-send a transfer just because the HTTP response never arrived. Confirm it truly did not happen first.
For deposits, build a reconciliation habit. GET /api/v2/ethereum/webhooks/notifications lists what was delivered; a nightly diff against your ledger catches anything a bug or outage swallowed, while the fix is still cheap.
Testnet: the same API with worthless coins
Add X-Network: testnet to any request and it executes on the test network. Routes, request bodies and response formats stay identical to mainnet, which means your integration tests exercise the real code path instead of mocks. Fund a test wallet from a public faucet, run transfers, receive webhooks — the full loop costs nothing.
Structure it so the header comes from configuration: staging sets it, production does not, and no code diff exists between the two. Give staging a separate callback URL as well, or test deposits will land in your production webhook handler. Going live is then the removal of one header, deliberately anticlimactic.
Integration in four steps
Get your API key. Registration is free and the trial starts without KYC.
Make your first request. Verify the key with GET /api/account, then import or create addresses.
Set up webhooks. Point notifications at your endpoint and verify the HMAC signature.
Go live. Remove the X-Network: testnet header; the identical code runs on mainnet.
What works on which chain
Ethereum sets the ERC-20 request pattern that BSC, Polygon and Arbitrum all follow with a changed path segment. The table below places it next to the other six chains the API covers.
| Chain | Addresses | Token transfers | Deposit webhooks |
|---|---|---|---|
| Bitcoin | POST /api/v2/bitcoin/wallets/{wallet}/addresses | — (no token standard) | GET /api/v2/bitcoin/webhooks/notifications |
| Ethereum | POST /api/v2/ethereum/addresses/import | ERC-20: POST /api/v2/ethereum/transactions/erc20 | GET /api/v2/ethereum/webhooks/notifications |
| TRON | POST /api/v2/tron/addresses/import | TRC-20 and TRC-10: POST /api/v2/tron/transactions/trc20 and .../trc10 | GET /api/v2/tron/webhooks/notifications |
| Solana | POST /api/v2/solana/addresses | SPL: POST /api/v2/solana/transactions/SPL | — |
| BNB Smart Chain | POST /api/v2/bsc/addresses/import | BEP-20: POST /api/v2/bsc/transactions/bep20 | GET /api/v2/bsc/webhooks/notifications |
| Polygon | POST /api/v2/polygon/addresses/import | ERC-20: POST /api/v2/polygon/transactions/erc20 | GET /api/v2/polygon/webhooks/notifications |
| Arbitrum | POST /api/v2/arbitrum/addresses/import | ERC-20: POST /api/v2/arbitrum/transactions/erc20 | GET /api/v2/arbitrum/webhooks/notifications |
Two footnotes to read the table correctly. First: TRON is the deepest integration on the platform. Beyond the routes above, the reference documents staking (POST /api/v2/tron/freeze and /delegate), chain parameters, and a self-signing pair — /transactions/trc20/build to construct a transaction and /transactions/broadcast to submit one you signed locally. If your compliance team insists that private keys never leave your servers, that build-and-broadcast pattern is your way in.
Second: a dash means the current reference documents no v2 route for that cell, not that the network is second-class. Bitcoin has no token standard, hence the empty token cell — native BTC runs through its own wallet model instead: create a password-encrypted wallet with POST /api/v2/bitcoin/wallets, derive deposit addresses under it, and send with POST /api/v2/bitcoin/transactions. Solana's reference covers address creation, SOL and SPL transfers, and balance and block lookups, but no webhooks yet. For anything not listed here, the API reference has the current state.
Frequently asked questions
Ready to integrate Ethereum?
Create an account at app.chaingateway.io/register, import a testnet wallet and send a Sepolia ERC-20 transfer. The full endpoint reference is in the docs, and plans and rate limits are on their own page.