DEV Community

unifyport for UnifyPort

Posted on • Originally published at unifyport.ai

WhatsApp Contact APIs: Add a Contact or Send a vCard?

Two Similar Requests, Two Different Operations

“Add this person to WhatsApp contacts” and “send this person’s contact card” may sound similar, but they describe two different API operations.

  • Add a contact changes the connected WhatsApp account’s address book.
  • Send a vCard delivers structured contact information inside a conversation.

Sending a vCard does not save that person to the connected account’s contacts. Adding a contact does not send anything to a user.

Choosing the wrong operation can create confusing workflows, duplicate mutations, and incorrect retry behavior.

Quick Decision Table

Requirement API operation Result
Save someone to the connected account’s contact list POST /v1/accounts/{account_id}/contacts/add Returns a contact object and identifiers
Share a person’s contact details in a chat POST /v1/messages with message.type: "contact" Returns an accepted message result
Save a customer and send a representative’s card Call both operations separately Two independent results and retry paths

The key distinction is state versus content:

Add contact
    -> changes account state

Send vCard
    -> sends conversation content
Enter fullscreen mode Exit fullscreen mode

Option 1: Add a Contact to the Connected Account

Use the add-contact operation when your application needs to save someone to the connected WhatsApp account’s contact list.

curl -X POST \
  "https://api.unifyport.ai/v1/accounts/acc_example/contacts/add" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "15550001111",
    "whatsapp_options": {
      "first_name": "Jane",
      "full_name": "Jane Doe",
      "sync_to_device_contacts": false
    }
  }'
Enter fullscreen mode Exit fullscreen mode

The request must contain at least one of:

  • phone_number
  • username

WhatsApp-specific fields belong inside whatsapp_options:

  • first_name
  • full_name
  • sync_to_device_contacts

Do not move these fields to the top level of the request.

A successful response returns the contact representation. Preserve identifiers such as id and conversation_id from the response.

Do not construct a conversation identifier from the phone number. The provider’s canonical identifier may use a different format, so the API response should remain the source of truth.

See the complete Add contact API reference.

Option 2: Send a vCard in a Conversation

Use the unified message endpoint when the requirement is to share contact details with a user or group.

curl -X POST \
  "https://api.unifyport.ai/v1/messages" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acc_example",
    "to": {
      "id": "15550002222@s.whatsapp.net",
      "type": "user"
    },
    "message": {
      "type": "contact",
      "contacts": [
        {
          "name": "Jane Doe",
          "phones": [
            {
              "number": "+15550001111",
              "type": "CELL"
            }
          ],
          "emails": [
            {
              "address": "jane@example.com"
            }
          ],
          "organization": "Example Company",
          "title": "Product Manager"
        }
      ]
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Every card in message.contacts requires a non-empty name.

The following fields are optional:

  • phones[].number
  • phones[].type
  • emails[].address
  • organization
  • title

You can send one or multiple contact cards in the same request.

The API generates the vCard from structured JSON, so you do not need to construct raw BEGIN:VCARD content manually.

See the complete Send contact message API reference.

Why These Calls Should Remain Separate

Consider a customer-support handoff:

  1. Save the customer to the connected WhatsApp account.
  2. Send the customer a contact card for the assigned account manager.

Although both steps involve contacts, they have different side effects.

Step 1: Add customer
Result: Address-book mutation

Step 2: Send account manager's card
Result: Outbound message
Enter fullscreen mode Exit fullscreen mode

Store the two results independently.

A successful address-book update does not prove that the contact message was accepted. An accepted contact message does not prove that the address book changed.

This separation becomes especially important during retries.

If sending the vCard fails after the contact was successfully added, retry only the message operation. Repeating the already successful address-book mutation adds unnecessary work and may produce provider-specific conflicts.

Model the Workflow Explicitly

A simple TypeScript workflow could look like this:

type ContactWorkflowResult = {
  addedContactId?: string;
  conversationId?: string;
  sentMessageId?: string;
};

async function addCustomerAndSendRepresentativeCard(
  accountId: string,
  customerPhone: string,
): Promise<ContactWorkflowResult> {
  const result: ContactWorkflowResult = {};

  const contact = await addContact({
    accountId,
    phoneNumber: customerPhone,
    whatsappOptions: {
      fullName: "Customer",
      syncToDeviceContacts: false,
    },
  });

  result.addedContactId = contact.id;
  result.conversationId = contact.conversation_id;

  const message = await sendContactMessage({
    accountId,
    to: {
      id: contact.conversation_id,
      type: "user",
    },
    contacts: [
      {
        name: "Jane Doe",
        phones: [{ number: "+15550001111", type: "CELL" }],
        organization: "Example Company",
        title: "Account Manager",
      },
    ],
  });

  result.sentMessageId = message.message_id;

  return result;
}
Enter fullscreen mode Exit fullscreen mode

In a production system, persist the successful result of each step before starting the next one. That allows a failed workflow to resume from the correct operation.

Handle Errors by Operation

Do not place every contact-related error into one generic handler.

Operation Error Meaning
Add contact 400 invalid_request Neither phone_number nor username is valid or present
Add contact 501 unsupported_by_provider The provider does not implement address-book contact creation
Send vCard 400 invalid_request The contacts array is empty or a card is missing name
Send vCard 400 unsupported_message_type The selected account does not support structured contact messages

A practical error-handling structure is:

try {
  await executeContactOperation();
} catch (error) {
  switch (error.code) {
    case "invalid_request":
      // Correct the request instead of retrying it unchanged.
      break;

    case "unsupported_by_provider":
    case "unsupported_message_type":
      // Use a documented fallback or disable the feature.
      break;

    default:
      // Apply bounded retry rules only to transient failures.
      throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

Validation and capability errors should not be retried with the same request.

For transient failures, use bounded retries and an idempotency strategy appropriate to the operation. Never treat a message retry as permission to repeat a completed address-book mutation.

Design a Cross-Provider Fallback

Structured contact messages are currently WhatsApp-specific in this integration.

Before exposing a universal “send contact” button across WhatsApp, Telegram, LINE, TikTok, Zalo, and X, check the provider message support matrix.

When structured cards are unavailable, a text fallback can preserve the essential information:

Jane Doe
Product Manager, Example Company
Phone: +1 555-000-1111
Email: jane@example.com
Enter fullscreen mode Exit fullscreen mode

Make the fallback explicit in your product design. Silently converting structured content can create inconsistent formatting and privacy behavior across channels.

Keep Personal Data Minimal

Contact cards can contain personal information. Only include fields needed for the actual workflow.

Avoid sending phone numbers, email addresses, job titles, or organization details simply because the schema allows them.

Also remember that sending a vCard does not force the recipient to save it. The recipient’s WhatsApp client and their own actions determine how the card is displayed and whether it becomes a saved contact.

Implementation Checklist

Before shipping, verify that:

  • The product requirement distinguishes address-book changes from chat messages.
  • Add-contact requests contain phone_number or username.
  • WhatsApp-specific fields are placed under whatsapp_options.
  • Returned contact and conversation identifiers are persisted.
  • Contact messages contain a non-empty contacts array.
  • Every card contains name.
  • Add and send results are recorded independently.
  • Retries resume from the failed step.
  • Capability errors do not enter an automatic retry loop.
  • Unsupported providers have an explicit fallback.
  • Only necessary personal data is included.
  • Tests confirm that sending a vCard does not trigger contact creation.

Final Takeaway

Use /contacts/add when you need to change the connected WhatsApp account’s contact list.

Use /v1/messages with message.type: "contact" when you need to deliver contact details inside a chat.

Some workflows need both, but they should remain two explicit operations with separate results, errors, and retry policies.


Originally published on UnifyPort.

Disclosure: AI-assisted drafting and editing were used to prepare this DEV adaptation. The technical content was reviewed against the linked API documentation.

Top comments (0)