Airtable x GREEN-API - develop your integration#
This integration lets you send WhatsApp, Telegram and MAX messages from an Airtable base and receive replies back into it through the GREEN-API platform. Built on the Universal Integration Platform by GREEN-API, it consists of two parts kept in the same repository: the Airtable extension (extension/) and the adapter service (src/).
Table of contents#
- Overview
- Architecture
- Installation
- How the integration works
- Adapter API
- What the adapter stores
- Development and tests
- License
Overview#
This documentation guides you through deploying your own copy of the integration between GREEN-API and Airtable. You will:
- Deploy the adapter service on a public HTTPS domain with a MySQL database.
- Register your own Airtable OAuth integration with the redirect URL of that domain and put its client id and client secret into
.env. - Create a custom extension in your Airtable base and connect the
extension/project to it. - Open the extension, set your adapter URL in
Settings→Advanced, confirm your identity and add instances.
Working with the integration after installation - confirming identity, collaborator permissions by role, sending and receiving messages - is described in Connecting the integration. This page covers only what you need to deploy and extend your own copy.
Architecture#
Airtable base ──(extension UI)──▶ adapter ──▶ GREEN-API ──▶ WhatsApp / Telegram / MAX
▲ │
└──(Airtable Web API, OAuth)───┘◀── webhooks (messages, statuses) ◀── GREEN-API
Airtable extension#
The Blocks SDK extension that:
- Holds the base settings: table and field mapping, sending settings
- Builds a message for each row and shows a preview
- Hands the batch of messages to the adapter and shows the result for each row
- Keeps the collaborator's personal key in the browser's local storage
Adapter service#
The NestJS application that:
- Keeps GREEN-API instance credentials. Nothing secret is stored in the Airtable base
- Confirms collaborators' identity through Airtable OAuth, checks their role in the base and issues personal keys
- Sends messages through GREEN-API and reports the result for each row
- Receives GREEN-API webhooks and writes incoming and outgoing messages and statuses into the base through the Airtable Web API
Installation#
Prerequisites#
- Node.js 24
- MySQL 8 (or MariaDB)
- A public HTTPS URL for the adapter - GREEN-API webhooks and OAuth callbacks must reach it
- A GREEN-API account and instance(s)
- A paid Airtable plan - custom extensions and the Marketplace are available on paid plans only
Registering the Airtable OAuth integration#
-
Name and register the integration.
-
Generate a client secret (
Generate a client secret). The token exchange is done by the adapter, which is a server, so Airtable requires it. Copy it into.env. -
Set the OAuth redirect URL:
<APP_URL>/api/v1/oauth/callback. -
Select scopes - exactly these four:
Scope Used for user.email:readidentifying the collaborator when they confirm identity schema.bases:readreading their role in the base, and the table and field names for receiving data.records:readfinding the contact to link an incoming message to data.records:writewriting message rows and statuses
PKCE is always used (Airtable requires it). The client secret is sent as HTTP Basic auth on the token and refresh requests. Access tokens live 60 minutes and are refreshed by the adapter. Refresh tokens live 60 days and are renewed whenever used.
Setting up the adapter#
1. Clone the repository:
```bash
git clone https://github.com/green-api/greenapi-integration-airtable.git
cd greenapi-integration-airtable
```
-
Install dependencies:
npm ci -
Configure environment variables:
cp .env.example .envFill in the following variables in
.env:Variable Meaning DATABASE_URLMySQL connection string, e.g. mysql://user:pass@host:3306/adapterAPP_URLpublic base URL of the adapter, e.g. https://your-adapter-domain.com. Used to build the OAuth redirect (/api/v1/oauth/callback) and the webhook URL (/api/v1/webhooks/green-api)PORTlisten port, default 3000AIRTABLE_CLIENT_IDclient id of the Airtable OAuth integration (step above) AIRTABLE_CLIENT_SECRETclient secret of the same integration. Required: the adapter refuses to start without it All variables are validated at startup.
-
Apply database migrations:
npx prisma migrate deploy -
Build and start the adapter:
# Build the application npm run build # Start in production mode npm run start:prod
Deployment using Docker#
The repository root contains a Dockerfile and a docker-compose.yml. To start, run:
docker compose up --build
The command runs database migrations and then starts the adapter.
Running and releasing the extension#
extension/ is a standard Blocks SDK project that is already initialised. Create the extension in the base and run it in development mode by following Install the extension in the base. While developing, keep in mind:
extension/.block/remote.jsonis per developer and git-ignored;- the
npm start,npm run releaseandnpm run submitscripts inextension/package.jsonrun a pinned Blocks CLI version (@airtable/blocks-cli@3.0.3) throughnpx, sonpm startcan be used instead ofblock run; - the dev server rebuilds the panel on every code edit;
- once it is running, set your adapter URL in the extension:
Settings→Advanced. The default URL is set by theDEFAULT_ADAPTER_URLconstant inextension/frontend/defaults.ts.
To publish:
npm run release # block release - pushes the build to the extension in the base
npm run submit # block submit - Marketplace review
A custom extension lives in one base. To use it in another base, add that base as another remote (block add-remote) and release again.
How the integration works#
Outgoing messages#
- The extension builds one message per row and hands the batch to the adapter (
POST jobs). Message types and per-messenger limits are described in Message types. - The adapter does not pace sends - the GREEN-API queue does, with the
delaySendMessagesMillisecondsinstance setting. - Files are sent by upload: when the adapter reaches the row, it downloads the attachment and uploads it to GREEN-API with
sendFileByUpload.
Sending without the extension panel - from Airtable automations and external services - is done by the POST send route with an automation key. The request and response format is described in Send from Airtable automations and other services.
Incoming messages#
- To write into the base, the adapter needs Airtable OAuth access with
data.records:read/writeandschema.bases:read. The owner's first identity confirmation already includes it. - When receiving is switched on for an instance, the adapter calls
setSettingswithwebhookUrl = <APP_URL>/api/v1/webhooks/green-apiand a randomwebhookUrlToken. Switching receiving off clears the webhook again. - The adapter writes each webhook into Airtable synchronously and answers GREEN-API with
200. If the write fails (Airtable rate limit, expired token, deleted field, Airtable outage), it answers503, and GREEN-API re-delivers the same webhook. - A re-delivered message that was already written is recognised by its
idMessageand not duplicated.
What the base owner sees when setting up receiving and how statuses are updated is described in Receive incoming messages.
Phone numbers and chat IDs#
How the extension recognises phone numbers, chat IDs and group IDs is described in Phone numbers and chat IDs. When sending, a phone number is normalised to <digits>@c.us: in WhatsApp - any value except a group ID (…@g.us), in Telegram and MAX - only a value formatted like a phone number.
GREEN-API methods#
What each tool does is described in Additional tools. The table below shows which GREEN-API methods it calls.
| Where in the extension | GREEN-API method |
|---|---|
Tools → Check numbers | checkWhatsapp / checkAccount |
Tools → Chat history | checkAccount, getChatHistory. For Telegram and MAX, a phone number is first resolved to the chat ID, because their journals are keyed by chat ID |
Tools → Import history | lastIncomingMessages, lastOutgoingMessages |
Tools → Import contacts | getContacts |
Instances → Link phone | qr |
Send tab, message queue | showMessagesQueue, clearMessagesQueue |
The links lead to the WhatsApp method descriptions.
Adapter API#
All routes are under /api/v1. Every route except oauth/* and webhooks/* needs Authorization: Bearer pk_… (a personal key). Rate limits: 300 requests per minute per IP; oauth/identity - 10 per minute.
| Method | Route | Who | Purpose |
|---|---|---|---|
| POST | oauth/identity | anyone | start identity confirmation for a base → authorization URL |
| POST | oauth/claim | anyone | exchange the completed authorization for the personal key |
| GET | oauth/callback | Airtable | OAuth redirect target |
| POST | oauth/base | owner | (re)grant the adapter Airtable access for the base |
| GET | bases/me | member | base view: Airtable connection, receiving config, instances, the caller |
| PUT / DELETE | bases/inbound | owner | set / clear the receiving configuration |
| DELETE | bases/airtable | owner | disconnect the base's Airtable access |
| GET | instances | member | instances with live state and phone |
| POST | instances | owner | add an instance (idInstance, apiTokenInstance, apiUrl, name?) |
| PATCH | instances/:id/receive | owner | switch receiving on / off |
| DELETE | instances/:id | owner | remove an instance (clears its webhook first) |
| GET | users/me, users | member / owner | the caller; everyone who confirmed identity |
| PUT | users/:id/instances | owner | which instances an editor may use |
| POST | users/:id/revoke, DELETE users/:id | owner | revoke a key / remove a person |
| POST | jobs | member | send a batch (idInstance, items[], writeBack?) |
| GET | jobs, jobs/:id | member | recent jobs; one job with per-row results |
| POST | jobs/:id/cancel | member | stop a running job |
| GET / POST | automation-keys | owner | list / create automation keys (the key itself is returned once) |
| DELETE | automation-keys/:id | owner | revoke a key |
| POST | instances/:id/check | member | check phone numbers on the instance's messenger |
| POST | instances/:id/history, instances/:id/chat-history | member | import the journal / one chat into the messages table |
| GET | instances/:id/queue, POST instances/:id/queue/clear | member | GREEN-API queue size; clear it |
| GET | instances/:id/qr | member | QR code for linking |
| GET | instances/:id/contacts | member | the messenger's contacts |
| POST | send | automation key | send one message and wait for the result (format) |
| POST | webhooks/green-api | GREEN-API | webhook receiver (Bearer webhookUrlToken) |
What the adapter stores#
Message content is not kept. The adapter database holds:
- the Airtable base ID, its receiving configuration and the base's Airtable OAuth tokens;
- people who confirmed identity: Airtable user ID, email, name, role, a hash of their personal key, and which instances editors may use;
- instances:
idInstance,apiTokenInstance,apiUrl, messenger, state, webhook settings; - automation keys: name, instance restriction, a hash of the key, when last used;
- send jobs: one row per batch and per recipient with status, GREEN-API message ID and error. The message payload is kept only while the row is queued and removed as soon as it is sent or fails.
Incoming webhooks are written straight into Airtable and not stored. The GREEN-API queue is the retry buffer.
Adapter logs
The greenapi-integration library logs request bodies and webhooks at info level. So message text and phone numbers do appear in the adapter's stdout unless log output is filtered.
Development and tests#
npm test # adapter unit tests (jest)
cd extension && npm test # extension unit tests
- Adapter: NestJS 11, Prisma 7 (
prisma/schema.prisma),src/defaults.tsfor every constant,src/errors.tsfor error codes. - Extension: React 16 and Blocks SDK 1.19. Config is stored in
globalConfigunder thegreenapikey (extension/frontend/config), message building is inextension/frontend/send/compose.ts, styling tokens are inextension/frontend/style.css.
License#
Licensed under MIT