# Salesforce: create or update record

The **Salesforce: create or update record** Point template upserts one record: it matches on an external id field, updates the record when the value is already there and creates it when it isn't.

Salesforce has no static API token, so this point authenticates through a connected app registered once for the whole account — there's no token field on the point itself.

## Before you start

Create a connected app in your Salesforce org and connect it under **Settings > 3rd-party integrations > Salesforce**:

1. In Salesforce, create an **External Client App** (or a classic connected app) with OAuth enabled and the **Manage user data via APIs (api)** scope.
2. Enable the **Client Credentials Flow** on it and pick the run-as user. The point writes as that user, so its profile decides which objects and fields it may touch.
3. In Pushwoosh, in **My Domain host**, enter your org's host without `https://`, for example `acme.my.salesforce.com`.
4. In **Consumer Key** and **Consumer Secret**, paste the app's credentials. The secret is stored write-only: Pushwoosh never shows it again, so paste a new one to replace it.
5. Click **Connect Salesforce**.

Two things also need to be in place on the Salesforce side:
* The field you match on has to be marked **External ID** on that object. A plain text field, even `Email`, isn't enough — Salesforce refuses the request with `NOT_FOUND` when the field isn't an external id.
* The connected app's Run As user needs field-level access to that field. A field created through Setup or the API grants nothing by itself, and without the permission Salesforce answers with the same `NOT_FOUND: Field name provided ... does not match an External ID` — the field is invisible to that user, so from the API's side it does not exist. Add it to a permission set assigned to the Run As user.

<Aside type="note">
A field marked External ID and Unique is the usual choice for a device or user identifier, for example a custom `Pushwoosh_HWID__c` on Contact.
</Aside>

## Configure the point

1. Drag **Salesforce: create or update record** from the **Integrations** section of the left panel onto the canvas.
2. Double-click the point and enter a **Step Name**.
3. In **My Domain host**, enter the same host the connected app lives on.
4. In **Object**, enter the object's API name — `Contact`, `Lead`, or a custom object ending in `__c`.
5. In **External id field**, enter the API name of the field marked External ID.
6. In **External id value**, enter the value to match on, usually a macro like `{{device:hwid}}`.
7. In **Fields**, add the field name mapped to the value to set — see [key/value fields](/product/customer-journey/journey-elements/integrations/#configuring-list-and-keyvalue-fields). Required fields of the object have to be among them, or Salesforce refuses to create a new record.
8. Click **Save**.

The record id comes back into the `salesforce_record_id` traveler attribute, so a later step can use it.

## Reading a failed request

Open [Calls log](/product/customer-journey/journey-elements/channels/webhook/#calls-log) in the point's drawer and check the response:

* **401** — the connected app's credentials are wrong, or the client credentials flow isn't enabled on it.
* **NOT_FOUND** in the body — the object name, or the external id field, doesn't match anything on that org; check that the field is really marked External ID.
* **REQUIRED_FIELD_MISSING** in the body — creating a new record needs a field the point doesn't send; add it in **Fields**.
* **INVALID_FIELD** in the body — a name in **Fields** isn't an API name on that object; a custom field's API name ends in `__c`.

<Aside type="caution">
The point sends its upsert as a composite request, and a composite call answers **200 even when the record wasn't written**: the verdict lives in the body, under `compositeResponse[0].httpStatusCode` and then `compositeResponse[0].body[0].success`. A rejected record therefore doesn't stop the traveler — it moves on as if the write succeeded, so check the Calls log when records don't show up.
</Aside>

A failed request drops the traveler from the journey — see [Errors and failed requests](/product/customer-journey/journey-elements/integrations/#errors-and-failed-requests).