Create a Notion database entry for every WhatsApp lead, query existing records before sending a personalised message, and keep your Notion workspace in sync with WhatsApp conversations automatically.
Before following this guide, read the External API Request step foundation guide. It covers every field so this guide can focus on Notion-specific values.
A valid Notion token does not automatically grant access to your databases. You must explicitly open each database in Notion, click Share, search for your integration name, and click Invite. Without this step, every API call to that database returns 403, even with a perfectly valid token.
Every Notion API call must include Notion-Version: 2025-09-03. Omitting it causes requests to fail or behave unexpectedly. This is the latest stable version as of June 2026.
secret_. Store it securely.notion.so/myworkspace/abc123def456...Notion API: developers.notion.com
Notion creates pages. A database entry is a page whose parent is the database. The request body must match the property types in your database schema.
Body:
{{
"parent": {{"database_id": "YOUR_DATABASE_ID"}},
"properties": {{
"Name": {{"title": [{{"text": {{"content": "Priya Sharma"}}}}]}},
"Email": {{"email": "priya@example.com"}},
"Phone": {{"phone_number": "+919820000001"}},
"Source": {{"select": {{"name": "WhatsApp"}}}},
"Status": {{"select": {{"name": "New"}}}}
}}
}}
Response (HTTP 200):
{{
"object": "page",
"id": "page-uuid-here",
"url": "https://www.notion.so/page-uuid-here",
"properties": {{ ... }}
}}
Map: id -> page_id, url -> notion_page_urlEach Notion property type uses a different JSON structure. The property name must match your database column name exactly.
| Notion property type | JSON structure in properties | Example value |
|---|---|---|
| Title (page name) | name: {title: [{text: {content: VALUE}}]} | "Name": {{"title": [{{"text": {{"content": "Priya"}}}}]}} |
| Rich text | name: {rich_text: [{text: {content: VALUE}}]} | "Notes": {{"rich_text": [{{"text": {{"content": "Enquired about demo"}}}}]}} |
| name: {email: VALUE} | "Email": {{"email": "priya@example.com"}} | |
| Phone number | name: {phone_number: VALUE} | "Phone": {{"phone_number": "+919820000001"}} |
| Select (single) | name: {select: {name: OPTION}} | "Status": {{"select": {{"name": "New"}}}} |
| Multi-select | name: {multi_select: [{name: OPT1}, {name: OPT2}]} | "Tags": {{"multi_select": [{{"name": "WhatsApp"}}, {{"name": "India"}}]}} |
| Number | name: {number: VALUE} | "Score": {{"number": 8}} |
| Date | name: {date: {start: ISO_DATE}} | "Date": {{"date": {{"start": "2026-06-23"}}}} |
| Checkbox | name: {checkbox: BOOL} | "Opted In": {{"checkbox": true}} |
| URL | name: {url: VALUE} | "Website": {{"url": "https://example.com"}} |
Property names in the JSON body must match your Notion database column names exactly, including capitalisation and spacing.
Every Notion database page must have a title property (the database's Name column or whatever you renamed it). The title uses a nested array structure: {{title: [{{text: {{content: 'Your Value'}}}}]}}. This is more verbose than other property types but is mandatory.
Before creating a duplicate, query the database to check if the contact already exists.
Body:
{{
"filter": {{
"property": "Email",
"email": {{"equals": "priya@example.com"}}
}},
"page_size": 1
}}
Response:
{{
"results": [
{{
"id": "page-uuid-here",
"url": "https://www.notion.so/...",
"properties": {{
"Name": {{"title": [{{"plain_text": "Priya Sharma"}}]}},
"Status": {{"select": {{"name": "Contacted"}}}}
}}
}}
],
"has_more": false
}}
results is empty -> contact not found -> create new entry
results has items -> map results[0].id, results[0].properties.*Trigger: Customer messages 'DEMO' on WhatsApp.
Bot collects:
{{customer_name}} = Priya Sharma
{{customer_email}} = priya@example.com
{{customer_phone}} = +919820000001
Step 1 — Check if already exists (POST /query):
POST https://api.notion.com/v1/databases/DB_ID/query
Body: {{filter: {{property:"Email", email: {{equals:"priya@example.com"}}}}}}
Response: results = [] (not found)
Conditions: if results not empty -> send existing-contact reply
if results empty -> run Step 2
Step 2 — Create entry (POST /pages):
POST https://api.notion.com/v1/pages
Body: {{parent: {{database_id: 'DB_ID'}},
properties: {{
Name: {{title: [{{text: {{content: "Priya Sharma"}}}}]}},
Email: {{email: "priya@example.com"}},
Phone: {{phone_number: "+919820000001"}},
Source: {{select: {{name: "WhatsApp"}}}},
Status: {{select: {{name: "New"}}}}
}}}}
Response: {{id: 'page-uuid', url: 'https://notion.so/...'}}
Bot replies:
'Thanks Priya! We have added you to our demo waitlist.
Our team will reach you on this number within 24 hours.'| Symptom | Likely cause | Fix |
|---|---|---|
| 403 on every request | Database not shared with the integration | Open the Notion database, click Share, search for your integration name, and click Invite. Token alone is not enough. |
| 400 on page creation | Wrong property type structure | Check the property type reference table. Title needs {title: [{text: {content: VALUE}}]}. Email needs {email: VALUE}. Match the structure exactly. |
| Property not saved | Column name mismatch | The property name in the JSON body must match the Notion column name exactly, including capitalisation and spaces. Check the column names in your database. |
| Notion-Version error | Missing version header | Add Notion-Version: 2025-09-03 to every request. Without this header, requests can fail or return unexpected results. |
| results empty on query | Email not in database or filter wrong | Test the filter with a known email first. Check that the property name 'Email' matches your column name exactly in the filter body. |
| 429 Too Many Requests | Exceeding 3 requests/second rate limit | Notion's rate limit is 3 requests per second. Add a brief pause between chained API calls (query then create) in your automation flow. |
Airtable REST API: PAT auth, create and search records by field.
Read guide →Log WhatsApp data to Google Sheets via Apps Script webhook.
Read guide →Free trial, no credit card. If you get stuck, we answer live on WhatsApp.