Skip to content

feat(contact): add contact save endpoint for Baileys provider (#2711) - #2712

Open
sohampirale wants to merge 2 commits into
evolution-foundation:mainfrom
sohampirale:feat/save-contact-endpoint
Open

feat(contact): add contact save endpoint for Baileys provider (#2711)#2712
sohampirale wants to merge 2 commits into
evolution-foundation:mainfrom
sohampirale:feat/save-contact-endpoint

Conversation

@sohampirale

@sohampirale sohampirale commented Aug 26, 2026

Copy link
Copy Markdown

📋 Description

Feat : #2711

This PR introduces a dedicated API endpoint for programmatically saving contacts to the connected WhatsApp account using Baileys' multi-device App-State Sync (collection critical_unblock_low).

Unlike POST /message/sendContact (which only sends a vCard message inside a chat), this endpoint updates WhatsApp's synchronized contact state so that the contact's name is recognized across all linked WhatsApp devices and the local database mirror.

Key Changes:

  • DTO & Validation: Added SaveContactDto and saveContactSchema with JSON Schema validation for number, name, optional firstName, and saveOnDevice.
  • Controller & Router: Implemented ContactController and ContactRouter exposing POST /contact/save/:instanceName.
  • Module Wiring: Registered ContactRouter and ContactController in index.router.ts and server.module.ts.
  • Baileys Service: Added saveContact() to BaileysStartupService utilizing Baileys' chatModify() with ContactAction (saveOnPrimaryAddressbook) and updating the local Prisma contact mirror.

🔗 Related Issue

Closes #2711


🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed against a live QR-paired WhatsApp session.

  • Functionality verified in development environment:

    • Sent POST /contact/save/:instanceName with test contact payload (number + name).
    • Verified contact is saved in WhatsApp's cloud contact state without sending any messages to the recipient.
    • Verified /chat/findContacts immediately reflects the updated contact with isSaved: true.
  • Full TypeScript type-check passed (npx tsc --noEmit with 0 errors).

  • ESLint & Prettier checks passed with 0 warnings/errors.

  • No breaking changes introduced to existing endpoints.


📸 Example Usage

Request:

POST /contact/save/my-instance
Content-Type: application/json
apikey: YOUR_API_KEY

{
  "number": "5511999999999",
  "name": "Jane Doe",
  "saveOnDevice": true
}

Response (201 Created):

{
  "saved": true,
  "number": "5511999999999",
  "name": "Jane Doe",
  "firstName": "Jane",
  "saveOnDevice": true
}

✅ Checklist

  • My code follows the project's style guidelines and patterns.
  • I have performed a self-review of my code.
  • My changes generate no new warnings or TypeScript errors.
  • I have manually tested my changes thoroughly on a live instance.
  • Git diff is clean and focused strictly on the contact save feature.

📝 Additional Notes

I am a new contributor to the Evolution API repository. I would appreciate any guidance, feedback, or suggestions from the maintainers if any adjustments are needed to better align with the project's standards and conventions. Thank you!

Summary by Sourcery

Enable programmatic contact saving for Baileys-backed WhatsApp instances.

New Features:

  • Add a validated POST /contact/save/:instanceName endpoint for saving contacts to connected WhatsApp accounts.
  • Persist saved contact details to the local contact mirror when contact storage is enabled.

Enhancements:

  • Integrate Baileys contact synchronization with configurable device address-book saving and automatic first-name handling.

Summary by Sourcery

Enable programmatic contact saving for Baileys-backed WhatsApp instances.

New Features:

  • Add a validated POST /contact/save/:instanceName endpoint for saving contacts on connected WhatsApp Baileys instances.
  • Synchronize saved contacts with WhatsApp and optionally persist them in the local contact mirror.

Enhancements:

  • Support configurable device address-book saving and automatic first-name derivation for saved contacts.

…ion-foundation#2711)

- Add SaveContactDto for typed contact payload
- Add saveContactSchema with validation for number and name
- Add ContactController delegating to active Baileys instance
- Add ContactRouter mounting POST /contact/save/:instanceName
- Register ContactRouter in index.router.ts and server.module.ts
- Implement saveContact in BaileysStartupService using chatModify contact app-state sync
@sourcery-ai

sourcery-ai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces POST /contact/save/:instanceName for validating and saving contacts through Baileys App-State Sync, with configurable device persistence, local Prisma mirroring, dependency wiring, and standardized error handling.

Sequence diagram for saving a contact through Baileys

sequenceDiagram
    participant Client
    participant ContactRouter
    participant ContactController
    participant BaileysStartupService
    participant WhatsApp
    participant Prisma

    Client->>ContactRouter: POST /contact/save/:instanceName
    ContactRouter->>ContactRouter: dataValidate
    ContactRouter->>ContactController: saveContact(instance, data)
    ContactController->>BaileysStartupService: saveContact(data)
    BaileysStartupService->>WhatsApp: chatModify(contact, jid)
    opt CONTACTS persistence enabled
        BaileysStartupService->>Prisma: contact.upsert
    end
    BaileysStartupService-->>ContactController: saved contact response
    ContactController-->>ContactRouter: response
    ContactRouter-->>Client: 201 Created
Loading

File-Level Changes

Change Details Files
Adds a validated API flow for saving contacts through the Baileys provider.
  • Defines the contact payload DTO and JSON Schema, requiring non-empty number and name while supporting firstName and saveOnDevice.
  • Exposes POST /contact/save/:instanceName behind the standard guards and returns a 201 response.
  • Wires the new controller and router into the server module and root router.
src/api/dto/contact.dto.ts
src/validate/contact.schema.ts
src/validate/validate.schema.ts
src/api/controllers/contact.controller.ts
src/api/routes/contact.router.ts
src/api/routes/index.router.ts
src/api/server.module.ts
Persists contacts to WhatsApp's synchronized address book and optionally mirrors them in the local database.
  • Builds the contact JID and derives firstName from the supplied name when omitted.
  • Uses Baileys chatModify with saveOnPrimaryAddressbook, defaulting saveOnDevice to true.
  • Upserts the contact mirror when contact persistence is enabled and returns a success payload.
  • Converts provider failures into an InternalServerErrorException with failure details.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Assessment against linked issues

Issue Objective Addressed Explanation
#2711 Provide a dedicated authenticated POST /contact/save/{instance} endpoint that accepts a WhatsApp number, contact name, and optional contact fields.
#2711 Programmatically save the contact in WhatsApp's synchronized contact/address-book state rather than sending a vCard message, while maintaining the local contact mirror where configured.
#2711 Clearly document the scope and limitations of server-side contact saving, especially whether native Android/iOS Contacts synchronization is supported. The implementation uses Baileys' contact action and the PR describes synchronized WhatsApp state, but it does not add documentation explicitly clarifying the native device Contacts limitation or other multi-device/primary-device behavior requested by the issue.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/validate/contact.schema.ts" line_range="27-28" />
<code_context>
+  $id: v4(),
+  type: 'object',
+  properties: {
+    number: { type: 'string' },
+    name: { type: 'string' },
+    firstName: { type: 'string' },
+    saveOnDevice: { type: 'boolean' },
+  },
+  required: ['number', 'name'],
+  ...isNotEmpty('number', 'name'),
+};
</code_context>
<issue_to_address>
**issue (bug_risk):** The non-empty checks for `number` and `name` are skipped whenever the request includes `firstName` or `saveOnDevice`. `isNotEmpty` uses `propertyNames` with an enum containing only `number` and `name`, so the `if` condition becomes false for those valid optional fields and payloads such as `{ "number": "", "name": "", "saveOnDevice": true }` pass validation.

**Triggers:** When a client supplies either optional field along with an empty required field.

**Suggested fix:** Validate each property directly with `minLength: 1`, or change the conditional schema so optional properties do not disable the required-field checks.

```suggestion
    number: { type: 'string', minLength: 1 },
    name: { type: 'string', minLength: 1 },
```
</issue_to_address>

### Comment 2
<location path="src/validate/contact.schema.ts" line_range="27-32" />
<code_context>
+  $id: v4(),
+  type: 'object',
+  properties: {
+    number: { type: 'string' },
+    name: { type: 'string' },
+    firstName: { type: 'string' },
+    saveOnDevice: { type: 'boolean' },
+  },
+  required: ['number', 'name'],
+  ...isNotEmpty('number', 'name'),
+};
</code_context>
<issue_to_address>
**issue (bug_risk):** The schema accepts any non-empty string as `number`, including values such as `abc` or whitespace. `createJid` strips non-digits and produces `@s.whatsapp.net`, so `chatModify` is called with a malformed JID instead of rejecting the request as invalid.

**Triggers:** When `number` is non-numeric or contains no digits.

**Suggested fix:** Require a valid WhatsApp number/JID pattern before calling `createJid`, and reject values that normalize to an empty number.
</issue_to_address>

### Comment 3
<location path="src/api/controllers/contact.controller.ts" line_range="8-9" />
<code_context>
+export class ContactController {
+  constructor(private readonly waMonitor: WAMonitoringService) {}
+
+  public async saveContact({ instanceName }: InstanceDto, data: SaveContactDto) {
+    return await this.waMonitor.waInstances[instanceName].saveContact(data);
+  }
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** The globally mounted route calls `saveContact` on whatever object is registered for `instanceName`, but only `BaileysStartupService` implements this method. Calling the endpoint for an Evolution or WhatsApp Business instance dereferences a missing method and returns an internal error instead of a provider-specific unsupported-operation response.

**Triggers:** When `POST /contact/save/:instanceName` targets a non-Baileys instance.

**Suggested fix:** Check the active instance/provider before dispatching and return a clear unsupported-provider error, or expose the route only for Baileys instances.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 3 findings to address first, and if the implementation is wrong, it can create or update an incorrect contact in the WhatsApp/device address book and optionally persist the wrong name in the local contact record. Reverting the code does not remove those entries, but the bounded damage can be repaired by correcting or deleting the affected contact.

Blocking findings: src/validate/contact.schema.ts:28, src/validate/contact.schema.ts:32, src/api/controllers/contact.controller.ts:9


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/validate/contact.schema.ts Outdated
Comment thread src/validate/contact.schema.ts Outdated
Comment thread src/api/controllers/contact.controller.ts Outdated
…provider checks

- Add numeric regex pattern and length constraints to number in saveContactSchema
- Replace conditional isNotEmpty with direct minLength constraints
- Add unsupported-provider guard in ContactController with BadRequestException
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] : Programmatic WhatsApp Contact Creation and Address-Book Synchronization

1 participant