Start for free

Create your Touchbase account or sign in to continue. We’ll bring you back to this page.

Start for freeSign in
← Developer documentation

Contact sync setup guide

Sync your platform’s users into Touchbase

Bring your platform’s existing users into Contacts, keep signups and updates synced, and choose the campaigns they receive.

Your platform’s users → your Touchbase contact lists → campaigns you control

Connect your own app, website, form, or CRM so its users become contacts in your Touchbase workspace. Your users do not need Touchbase accounts. Contact sync uses your workspace plan. It does not require a developer API key or prepaid API credit, and receiving a contact does not start a new email campaign.

What you can sync from your platform

Use the same contact format for different lifecycle events. Create named lists and webhook URLs for the audiences you want to email, then have your platform send the relevant users to each URL.

Your platform eventUseful fieldsCampaign use
user.signed_upEmail, name, signup_date, planWelcome or onboarding reminder
user.updatedEmail plus changed profile fieldsKeep names and campaign merge fields current
subscription.updatedEmail, plan, subscription_statusAdd the appropriate users to a product-update or subscriber list
subscription.cancelledEmail, plan, cancellation_dateSend eligible contacts to a separate win-back list
user.inactiveEmail, last_seen, inactivity_daysReactivation list; your platform determines inactivity
purchase.completedEmail, product_name, order_dateCustomer follow-up or product education
customer.unsubscribedEmail, subscribed: falseSuppress further marketing mail

Event names describe the update; they do not create campaigns or route contacts to different lists automatically. Your platform chooses the destination URL. For example, send signups to the Welcome list and inactive users to a separate Reactivation list. A subscription cancellation does not imply an unsubscribe unless you also send an opt-out flag.

1. Create your private webhook URL

  1. Sign in and open Contacts, then Sources and lists. You can also find Webhook under Integrations → Contact sources.
  2. Choose Webhook → Connect (or add another source if one is already connected).
  3. Name the list, such as “Website signups”. Under Save contacts under, choose Customers or Investors. Use a separate list and webhook for each group.
  4. Choose the statement that describes your relationship with these contacts and confirm it. This is separate from the customer/investor group.
  5. Select Make the webhook, then copy the full URL. Save it now: the secret is shown once.

Already created one but lost the URL? Open Contacts → Sources and lists, find the list, and choose New webhook address. Replace the URL in your website or automation; the old URL stops working immediately.

2. Sync existing users, then connect ongoing events

Send an HTTPS POST to the exact URL you copied, with Content-Type: application/json. The secret URL is the credential; no Authorization header is required. Keep it in a server environment variable, not browser JavaScript, a public repository, screenshots, or analytics logs.

For an automation tool, choose an action that sends an HTTP request. Set method to POST, paste the URL, select a JSON body, and map your signup or CRM fields to the fields below. For the first sync, have your platform export or send its existing users in batches (or upload a CSV under Contacts). Then connect signup, profile-update, subscription, and unsubscribe events for ongoing sync. Your platform or connector sends the selected users to the URL; simply saving a URL does not retrieve its historical database.

Replace YOUR_WEBHOOK_SECRET with your real secret, and you@example.com with an email you control. This example adds or updates a contact when you run it; it does not send an email by itself.

New signup request
curl --request POST 'https://touchbasetechnologies.com/api/hooks/audience/YOUR_WEBHOOK_SECRET' \
  --header 'Content-Type: application/json' \
  --data '{
  "event": "user.signed_up",
  "data": {
    "email": "you@example.com",
    "first_name": "Sam",
    "last_name": "Lee",
    "properties": {
      "platform_user_id": "usr_123",
      "plan": "starter",
      "signup_date": "2026-10-29"
    },
    "subscribed": true
  }
}'
FieldRequired?What to send
emailYesA person’s email address. Shared inboxes such as info@ or sales@ are rejected.
first_name / last_nameNoThe person’s name. A single name field is also accepted.
companyNoTheir company, if relevant. Individuals do not need a company value.
plan, signup_date, or other fieldsNoSend strings, numbers, or booleans directly on the contact, or in properties, attributes, traits, or metadata. These fields become campaign merge fields. Deeper nested objects are ignored.
subscribedNoUse false when the person opts out. Only send true when it accurately reflects your records. Existing opt-outs are not undone by a later true value.
Server-side JavaScript example

Most normal contact envelopes are supported: a single contact, an array, contacts/customers/users arrays, or a contact/user/customer under data, payload, or record. Map vendor-specific payloads to the documented format if they do not include an email. If your provider signs its webhooks, verify that signature in your own server or connector before forwarding the normalized contact.

Store the private URL as TOUCHBASE_CONTACT_WEBHOOK_URL in your server’s hosting settings. Adapt the user fields to your own application. Call this from a background job so a temporary sync problem does not block signup.

Server-side JavaScript
// Server-side code only. Save the URL in your hosting environment.
const webhookUrl = process.env.TOUCHBASE_CONTACT_WEBHOOK_URL;

export async function syncContact(user, event = 'user.updated') {
  if (!webhookUrl) throw new Error('Contact webhook URL is missing');
  const response = await fetch(webhookUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      event,
      data: {
        email: user.email,
        first_name: user.firstName,
        last_name: user.lastName,
        properties: { platform_user_id: user.id, plan: user.plan },
        subscribed: user.acceptsMarketing
      }
    })
  });
  const result = await response.json();
  if (!response.ok) throw new Error('Contact sync failed: HTTP ' + response.status);
  // HTTP 200 can still contain rejected rows or skipped contacts.
  if (result.rejected?.length || result.truncated || result.capacitySkipped) {
    throw new Error('Contact sync needs attention; inspect the response counts');
  }
  return result;
}
Import existing contacts in batches

Send up to 500 contacts per request and keep each JSON request below 256 KB. A list can hold up to 20,000 contacts. For larger imports, split the list into batches. There is a limit of 60 requests per minute.

Batch JSON body
{
  "contacts": [
    {
      "email": "you@example.com",
      "first_name": "Sam"
    },
    {
      "email": "another@example.com",
      "first_name": "Alex"
    }
  ]
}

Matching is by email address within your workspace. A platform_user_id is stored as a custom field; it does not replace the required email or rename an old email address. Resending the same email updates the existing workspace contact instead of creating a second contact. Partial updates merge into existing list fields: a plan change does not erase an earlier signup_date. When the same field is supplied again, the last processed value wins. A contact’s original list-join time is preserved, so repeated profile updates do not restart a signup reminder. This is contact sync, not a full historical event log.

Sync unsubscribes and deleted-user events

Send subscribed: false on a contact to suppress future mail. Event types containing unsubscribe or opt-out, and customer.deleted, user.deleted, contact.deleted, or subscriber.deleted, also suppress the supplied addresses. This stops marketing mail; it does not permanently delete the Touchbase contact or its history.

Unsubscribe JSON body
{
  "type": "customer.unsubscribed",
  "data": {
    "email": "you@example.com",
    "subscribed": false
  }
}

3. Check the response and find the contact

A successful request returns HTTP 200. For one new contact, the response looks like this:

Example success response
{
  "added": 1,
  "existing": 0,
  "blocked": 0,
  "optedOut": 0,
  "capacitySkipped": 0,
  "rejected": [],
  "truncated": false
}
  • added: new contacts created. existing: contacts already known to this workspace.
  • blocked: contacts held back from email, including previous opt-outs or bounces. optedOut: addresses suppressed by this request.
  • rejected: unusable rows, with the row number and reason. A 200 response can still include rejected rows.
  • truncated: only the first 500 contacts were read. capacitySkipped: new contacts omitted because the list is full. Send only the missing contacts again after resolving the limit.

Open Contacts → Customers or Contacts → Investors, matching the group chosen when creating the URL. Refresh the page, search for the email you sent, and use the list filter if needed. Under Sources and lists, you can also open the list to see its people. An unsubscribe-only event for a new address can suppress it without adding it to the list.

4. Create a campaign or an optional signup reminder

  1. Open Outreach → Campaigns and create a campaign.
  2. Choose Customers or Investors, then select the webhook’s named list if you want to keep receiving future signups. “All contacts” creates a selection of the contacts available at that time.
  3. Choose the email design, edit the subject and content, review the preview, and choose a connected sending mailbox.
  4. To send once to each future signup, open the campaign’s New-signup reminders section, set the delay in hours, and choose Save and enable rule. Then review and start the campaign to activate sending. Create another campaign for a later reminder.

New-signup reminders are off until enabled. If you already enabled and started a reminder campaign for this list, incoming contacts can receive that email after its delay. Suppressed, bounced, and otherwise unsendable contacts are excluded. Each reminder is once per future list enrollment, not once per event: sending user.updated repeatedly does not resend that reminder. For another message or audience, configure another campaign/list explicitly.

Troubleshooting

What you seeWhat to do
400 / invalid JSONUse POST with Content-Type: application/json and a contact with an email, or a contacts array. Check the actual response message.
403Check that the workspace is active and has access to contact imports. A suspended workspace cannot receive contacts.
404Copy the correct current URL. A replaced secret, a deleted list, or a mistyped URL is not accepted.
413Reduce the JSON request size below 256 KB.
429Slow down. Honor Retry-After when supplied; otherwise wait and retry with increasing delays.
5xx / connection timeoutRetry with bounded backoff. Reusing the same email avoids a duplicate contact. Investigate repeated failures.
200, but added is 0Check existing, rejected, blocked, optedOut, capacitySkipped and truncated. A known contact increments existing; it is not added again.
Contact is missing from the viewCheck the selected customer/investor group, list filter, and search text, then refresh. Read rejected rows in the response.
Contacts arrive, but no email sendsImporting is separate from sending. Review and start a campaign; for future signups, select the named webhook list and explicitly enable its reminder rule.

Need help? Contact Touchbase support with the HTTP status, time, and response counts. Remove the secret URL and personal contact data from anything you share.

Stop waiting for customers to find you

Tell Touchbase what you sell. It finds the companies that need it and the person to write to at each, and drafts a first email for you to read and send.