customer.created webhook
The customer.created event is delivered whenever one or more new customer records are created in your Customerscore.io account — whether you create them through the REST API, upload a CSV, or an integration sync (HubSpot, Salesforce, Stripe, …) brings in customers that did not exist before. Use it to mirror new accounts into your own systems, kick off onboarding automations, or keep an external ID mapping in sync.
Heads-up on batching: a single import can create thousands of customers at once. Customerscore.io groups newly created customers into batches of up to 1000 and sends one
customer.createdrequest per batch. Always read thebatchobject (below) to know whether more requests are coming.
Heads-up on timing: the event fires at the moment the record is inserted, before any scoring or attribute processing runs. The payload therefore carries only identity fields —
customerscore_id,external_id,name,created_at— and the importsource. Health score, churn risk, owner, tags and custom properties are not available yet. Fetch them later with Get Customer when you need them.
When it's triggered
A customer.created request is dispatched when all of the following are true:
- Your account has at least one active webhook subscribed to the
customer.createdevent. - A customer record (not a contact) with an
external_idthat did not previously exist in your account is inserted.
Only genuinely new records fire the event. Re-importing or re-syncing a customer whose external_id already exists updates it silently and produces no webhook.
| Source | data.source | Description |
|---|---|---|
Create Customer (POST /v1/customer) | API | One customer per request; the webhook is dispatched immediately after the record is created. |
Customer upload (POST /v1/customers) | API | Bulk upload. New external_ids are created while the upload is processed in the background. Only when the API is your account's source of truth. |
| CSV import from the app | FILE | Only when file import is your account's source of truth. |
| Stripe integration | STRIPE | New Stripe customers found during a sync. Only when Stripe is your account's source of truth. |
| CRM / data-source integrations (HubSpot, Salesforce, Pipedrive, Raynet, PostHog, Mixpanel, …) | HUBSPOT, SALESFORCE, PIPEDRIVE, RAYNET, POSTHOG, … | New records found during a scheduled sync of the integration configured as your account's source of truth. The value is the integration's identifier in UPPERCASE. |
Contacts (people belonging to a customer) never fire this event.
Delivery
| Property | Value |
|---|---|
| HTTP method | POST |
Content-Type | application/json |
User-Agent | Customerscore.io-Webhook/1.0 |
| Signature header | X-customerscore-HMAC: sha256=<hex> (see Verifying the signature) |
| Timeout | 10 seconds |
| Retries | Up to 3 attempts, exponential backoff (first retry after ~5s) |
Response handling:
- 2xx — treated as a successful delivery; no retry.
- 5xx — treated as a transient failure and retried (up to the retry limit).
- 4xx — treated as a permanent failure; not retried. Make sure your endpoint returns a 2xx once it has accepted the payload.
Your endpoint should respond quickly (within the 10s timeout). If you need to do heavy processing, acknowledge with a 2xx immediately and process the payload asynchronously.
Because failed deliveries are retried, your endpoint may occasionally receive the same batch twice. Treat customerscore_id as the idempotency key.
Payload schema
Every webhook is wrapped in a common envelope:
{
"event": "customer.created",
"timestamp": "2026-09-03T10:00:00.000Z",
"data": { ... }
}| Field | Type | Description |
|---|---|---|
event | string | Always "customer.created" for this event. |
timestamp | string (ISO-8601) | When the webhook payload was generated (UTC). |
data | object | The event payload — see below. |
data
| Field | Type | Nullable | Description |
|---|---|---|---|
source | string | no | Which import created the customers — see data.source. |
customers | array | no | The newly created customers in this batch — see data.customers. |
batch | object | no | Batching metadata — see data.batch. |
data.source
An UPPERCASE identifier of the data source that created the customers. Common values:
| Value | Meaning |
|---|---|
API | REST API — single create or bulk upload. |
FILE | CSV import from the app. |
STRIPE | Stripe integration sync. |
HUBSPOT, SALESFORCE, PIPEDRIVE, RAYNET, … | The corresponding CRM / data-source integration sync. |
New integrations may introduce new values over time, so treat this field as an open set.
data.customers
Each entry represents one newly created customer.
| Field | Type | Nullable | Description |
|---|---|---|---|
customerscore_id | number | no | Customerscore.io's internal ID of the record. Identical to customerscore_id returned by Get Customer. |
external_id | string | no | Your identifier for the customer (the ID you sync to Customerscore.io). Use it with GET /v1/customer/{external_id}. |
name | string | yes | The customer's name as known at creation time. May be null or empty if the source did not provide one. |
created_at | string (ISO-8601) | no | When the record was inserted (UTC). |
data.batch
| Field | Type | Description |
|---|---|---|
current_batch | number | 1-based index of this batch. |
total_batches | number | Total number of batches in this group of created customers. |
total_customers | number | Total customers created in this group, across all batches. |
batch_size | number | Number of customers in this batch (≤ 1000). |
Note: bulk imports are processed in chunks. A large import (for example an initial CRM sync of 5,000 customers) is inserted in several independent chunks, and each chunk produces its own batch group with its own
total_customers. Do not rely ontotal_customersto equal the total size of the whole import — collect customers across requests and deduplicate oncustomerscore_id.
Verifying the signature
Every request includes an X-customerscore-HMAC header so you can confirm it came from Customerscore.io and was not tampered with. The value is:
sha256=<hex>where <hex> is the HMAC-SHA256 of the raw request body (the exact bytes you received, before any JSON parsing), keyed with your webhook's secret key.
The verification steps and Node.js / Python examples are identical for every event — see Verifying Webhook Signatures in the overview.
Example payloads
Single customer created via the API
{
"event": "customer.created",
"timestamp": "2026-09-03T10:00:00.412Z",
"data": {
"source": "API",
"customers": [
{
"customerscore_id": 98765,
"external_id": "acme-001",
"name": "Acme Corporation",
"created_at": "2026-09-03T10:00:00.389Z"
}
],
"batch": {
"current_batch": 1,
"total_batches": 1,
"total_customers": 1,
"batch_size": 1
}
}
}Batch created by a HubSpot sync
{
"event": "customer.created",
"timestamp": "2026-09-03T04:00:12.001Z",
"data": {
"source": "HUBSPOT",
"customers": [
{
"customerscore_id": 98766,
"external_id": "hs-1180234",
"name": "Beta Industries",
"created_at": "2026-09-03T04:00:11.870Z"
},
{
"customerscore_id": 98767,
"external_id": "hs-1180235",
"name": "Gamma LLC",
"created_at": "2026-09-03T04:00:11.870Z"
}
],
"batch": {
"current_batch": 1,
"total_batches": 1,
"total_customers": 2,
"batch_size": 2
}
}
}Handling the event
A typical handler upserts the mapping between your ID and Customerscore.io's ID, then optionally enriches the record once scoring has run:
// express handler — signature already verified (see the overview)
async function onCustomerCreated(payload) {
const { source, customers, batch } = payload.data;
for (const c of customers) {
await db.upsert("customerscore_customers", {
customerscore_id: c.customerscore_id, // idempotency key
external_id: c.external_id,
name: c.name,
created_at: c.created_at,
source,
});
}
console.log(
`batch ${batch.current_batch}/${batch.total_batches} — ${batch.batch_size} new customers from ${source}`
);
}