Conexiones de CRM
Mantén los contactos de Postal y tu CRM sincronizados, en los dos sentidos y sin bucles: HubSpot, Pipedrive y RD Station CRM, con mapeo de campos y etapas, registro y reintentos.
#Visión general
Una conexión de CRM mantiene los contactos de Postal y las personas de tu CRM sincronizados, en los dos sentidos y sin bucles. Lo que la persona hace en el CRM (un contacto nuevo, un negocio que cambia de etapa) llega a Postal como contacto, etiqueta, propiedad y evento, y puede iniciar una automatización. Lo que sucede en Postal (un contacto creado o modificado, un correo abierto, con clic, respondido o rebotado) vuelve al CRM como datos de la persona y como nota en su línea de tiempo.
| Conexión | Credenciales | Del CRM a Postal | De Postal al CRM |
|---|---|---|---|
| HubSpot | Token de una aplicación privada (y, para webhooks, el client secret) | Webhooks de contactos y negocios, y la sincronización manual | Contactos creados o actualizados; notas en la línea de tiempo |
| Pipedrive | Token de la API y dirección de la cuenta | Webhooks de personas y negocios, y la sincronización manual | Personas creadas o actualizadas; notas en la persona |
| RD Station CRM | Token de la instancia | Webhooks de contactos y negociaciones, y la sincronización manual | Contactos creados o actualizados; actividades en la negociación o nota en el contacto |
| Webhook (otro CRM) | Ninguna: un secreto de firma generado por Postal | Cualquier sistema que haga un POST con JSON firmado | POST firmado a la URL HTTPS de tu sistema |
Las tres conexiones nativas usan el token que pegas (sin OAuth) y leen la API del CRM por ti. La conexión por webhook sirve para cualquier CRM o sistema que envíe y reciba webhooks; tiene su propia página.
Las integraciones con apps (Stripe, Supabase, Clerk, Auth0 y Firebase) usan las mismas conexiones, pero se cuentan aparte, en app_integrations, y no sincronizan de vuelta.
Depende del plan
El número de conexiones de CRM viene del plan de marketing (crm_connections; 0 significa no incluido). Superar el límite responde 403 plan_limit_reached. Consulta Límites; GET /crm/providers muestra el límite y cuántas has usado ya. Las conexiones y los contactos creados por ellas cuentan en los límites del plan como cualquier otro.
#Crear una conexión
En el panel, abre Integraciones › CRM, elige el CRM y pega las credenciales. Se guardan cifradas y no vuelven a mostrarse: la API devuelve solo los nombres de los campos guardados. Usa Probar conexión para comprobar el acceso. Por la API, es POST /crm-connections, con una clave de acceso total.
cURL
curl -X POST https://api.postal.so/crm-connections \
-H "Authorization: Bearer $POSTAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "hubspot",
"name": "HubSpot",
"credentials": {
"access_token": "pat-na1-…",
"client_secret": "…"
},
"settings": {
"direction": "both"
}
}'
Node.js
const response = await fetch('https://api.postal.so/crm-connections', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'hubspot',
name: 'HubSpot',
credentials: {
access_token: 'pat-na1-…',
client_secret: '…',
},
settings: {
direction: 'both',
},
}),
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/crm-connections",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
json={
"provider": "hubspot",
"name": "HubSpot",
"credentials": {
"access_token": "pat-na1-…",
"client_secret": "…",
},
"settings": {
"direction": "both",
},
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('crm-connections', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'json' => [
'provider' => 'hubspot',
'name' => 'HubSpot',
'credentials' => [
'access_token' => 'pat-na1-…',
'client_secret' => '…',
],
'settings' => [
'direction' => 'both',
],
],
]);
$data = json_decode($response->getBody(), true);
{
"object": "crm_connection",
"id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"provider": "hubspot",
"name": "HubSpot",
"status": "active",
"settings": {
"direction": "both",
"field_mappings": [],
"stage": null,
"inbound": {
"items_path": null,
"event_field": null,
"allow_token": false
},
"outbound": {
"url": null,
"events": [
"contact.created",
"contact.updated",
"contact.deleted",
"email.opened",
"email.clicked",
"email.replied",
"email.bounced",
"email.complained"
]
},
"skip_sources": []
},
"credentials": ["access_token", "client_secret"],
"source": "crm:019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"webhook_url": "https://api.postal.so/integrations/crm/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/webhook",
"webhook_secret": "whsec_c2VncmVkby1kZS1leGVtcGxvLTEyMzQ1Ng==",
"last_sync_at": null,
"last_error": null,
"last_error_at": null,
"created_at": "2026-10-26T14:00:00.000000Z",
"updated_at": "2026-10-26T14:00:00.000000Z"
}
provider:hubspot,pipedrive,rdstation_crmogeneric(el webhook). Las credenciales de cada uno están más abajo.statusempieza enactive.paused(PATCH) deja de recibir y de enviar sin borrar nada;errores solo un aviso de que el último envío falló definitivamente y vuelve aactivecon el primer envío que funcione.webhook_urles donde el CRM entrega los cambios;webhook_secret(whsec_…) es el secreto que autentica la entrega. Aparecen al crear, al consultar una conexión y al generar un secreto nuevo, no en la lista.source(crm:<id>) es el origen de todo lo que la conexión cambia en los contactos.
#Qué se sincroniza y en qué sentido
settings.direction elige el sentido: both (por defecto), inbound (solo del CRM a Postal) u outbound (solo de Postal al CRM). En una conexión solo de envío, el webhook del CRM responde 409 connection_paused; en una solo de recepción, Postal no envía nada.
Del CRM a Postal
- Cada persona se convierte en un contacto creado o actualizado. Postal reconoce el contacto por el vínculo con el registro del CRM, después por el
external_idy por último por el correo; por eso un cambio de correo en el CRM actualiza el mismo contacto. - Los campos mapeados se convierten en nombre, propiedades y etiquetas (consulta Mapear campos y etapas). Quien está en la lista de supresión no se convierte en contacto (la entrada queda como
skipped). - Quien canceló la suscripción nunca es vuelto a suscribir por un CRM:
unsubscribed: falseno deshace una cancelación (solo la API de contactos, con la prueba del nuevo consentimiento). - Un evento del CRM, cuando el mapeo nombra uno (
inbound.event_field), se guarda en el contacto y puede iniciar una automatización con el disparador de evento. - Las eliminaciones en el CRM no borran el contacto en Postal.
De Postal al CRM
Postal envía los cambios que el CRM suscribió en settings.outbound.events. El valor predeterminado es:
| Evento | En el CRM | Predeterminado |
|---|---|---|
contact.created, contact.updated | La persona se crea o se actualiza (nombre, correo, campos mapeados). | Sí |
contact.deleted | Una nota “Contato excluído no Postal”. El registro del CRM no se borra. | Sí |
email.opened, email.clicked, email.replied, email.bounced, email.complained | Una nota en la línea de tiempo de la persona, con el asunto (y el enlace, el motivo del rebote…). | Sí |
email.sent, email.delivered, email.failed, email.suppressed | Lo mismo, para quien quiere el historial completo. | No |
Los eventos son los mismos de los webhooks de Postal. Cualquier otro nombre en outbound.events da 422.
El texto de las notas está siempre en portugués de Brasil, como lo leen las personas del CRM (por ejemplo, “Postal: E-mail aberto”, “Assunto: …”). Los nombres de campos que Postal guarda en el CRM no cambian con el idioma.
Sincronizar ahora
El botón Sincronizar ahora (POST /crm-connections/{id}/sync) se ejecuta en segundo plano: lee las personas del CRM, página a página (y, si hay mapeo de etapas, los negocios abiertos), y después envía todos los contactos de Postal, según el sentido. Cada persona aparece en el registro, y una entrada sync resume cuántas personas se importaron y enviaron y cuántas fallaron. Sirve para la primera carga, para recuperar lo que un webhook perdió o cuando el CRM no puede enviar webhooks.
#Mapear campos y etapas
Sin mapeo, Postal lee y escribe en su formato propio. En las conexiones nativas, el correo, el nombre, el apellido y el ID del registro ya vienen enlazados, y lo que mapeas se suma a ellos. Cada mapeo enlaza un campo del CRM con un campo de Postal, con un sentido propio (both, inbound u outbound):
| Campo de Postal | Qué es |
|---|---|
email | El correo electrónico del contacto. |
external_id | El ID de la persona en tu app (el mismo external_id de los contactos). |
crm_id | El ID del registro en el CRM. Postal lo guarda en el vínculo de la conexión, nunca en el external_id. |
first_name, last_name | Nombre y apellido. Un valor vacío en el CRM limpia el campo. |
unsubscribed | Verdadero o falso. Consulta la regla de la cancelación de suscripción más arriba. |
tags | Etiquetas: una lista, o un texto separado por comas o punto y coma. |
properties.<chave> | Una propiedad del contacto que ya existe (properties.company). Una clave que no existe da 422. |
El campo del CRM es una ruta al estilo JSONPath, de hasta 255 caracteres, con un subconjunto pequeño: $.person.email (o person.email), items[0].name, $.data.items[*] (todos los elementos de una lista) y $ (el documento completo). Una ruta que no existe lee null, y el campo se ignora (salvo nombre y apellido, que quedan vacíos). En las conexiones nativas, las rutas apuntan al registro que Postal construye a partir del CRM (por ejemplo, $.properties.company en HubSpot y $.custom.<chave> en Pipedrive y en RD Station CRM); GET /crm-connections/{id}/fields lista los campos del CRM que puedes elegir, con el tipo.
Etapas del embudo
Cuando un negocio cambia de etapa, su persona recibe una propiedad y/o una etiqueta de Postal, y se puede disparar una automatización con ese cambio. En settings.stage, crm_field y pipeline_field indican dónde están la etapa y el embudo (en las conexiones nativas, $.deal.stage y $.deal.pipeline ya son el valor por defecto), y cada elemento de mappings enlaza una stage (y, opcionalmente, un pipeline) con una property_key con property_value, con una tag o con las dos. Los nombres se comparan sin distinguir mayúsculas de minúsculas. Sin property_value, la propiedad recibe el nombre de la etapa. Al enviar, Postal no mueve negocios en el CRM. GET /crm-connections/{id}/stages lista las etapas de cada embudo.
{
"settings": {
"direction": "both",
"field_mappings": [
{
"crm_field": "$.properties.company",
"postal_field": "properties.company",
"direction": "both"
},
{
"crm_field": "$.properties.hs_lead_status",
"postal_field": "tags",
"direction": "inbound"
}
],
"stage": {
"crm_field": "$.deal.stage",
"pipeline_field": "$.deal.pipeline",
"mappings": [
{
"stage": "Closed won",
"pipeline": "Sales",
"property_key": "lifecycle",
"property_value": "customer",
"tag": "customer"
}
]
},
"outbound": {
"events": [
"contact.created",
"contact.updated",
"email.opened",
"email.clicked",
"email.replied"
]
},
"skip_sources": [
"my-app"
]
}
}
En este ejemplo, el campo company de HubSpot va y vuelve a la propiedad company de Postal, el estado de lead solo entra como etiqueta, y un negocio en “Closed won” del embudo “Sales” marca a la persona con lifecycle = customer y la etiqueta customer. Las propiedades company y lifecycle deben existir antes. Solo los eventos listados salen hacia el CRM, y los cambios que vienen de my-app no vuelven a él.
#Sin bucles
Dos conexiones que se alimentan entre sí podrían intercambiar el mismo cambio para siempre. Postal lo evita de dos maneras:
- Todo lo que una conexión cambia en un contacto lleva el origen
crm:<id>(last_change_source), y esa conexión no reenvía el cambio que ella misma hizo. Las otras conexiones del equipo reciben el cambio con normalidad. settings.skip_sourceslista orígenes cuyos cambios la conexión no envía (hasta 20; letras minúsculas, números,.,_,:y-; un*al final vale como prefijo). Úsalo para el sistema que escribe en Postal y también en el CRM, por ejemplo tu app, que envía contactos por la API con la etiqueta de origenmy-app, ycrm:*para ignorar todas las conexiones de CRM.
#Conexiones nativas
Las tres funcionan del mismo modo: Postal llama a la API del CRM con tu token (con un tiempo límite de 10 segundos, sin seguir redirecciones), busca a la persona por el vínculo guardado y después por el correo, y crea o actualiza el registro. Si un CRM responde 429, se espera el tiempo que pida, y un 5xx se reintenta. Las personas sin correo se ignoran. El mensaje de error del CRM aparece en el registro y nunca incluye el token.
Para recibir cambios del CRM hay que configurar el webhook en el propio CRM, con la URL de la conexión (webhook_url); sin webhook, la conexión aún envía datos y lee el CRM cuando pides Sincronizar ahora. Cada CRM autentica el webhook de una forma:
| CRM | Cómo comprueba Postal el webhook |
|---|---|
| HubSpot | Firma X-HubSpot-Signature-v3, calculada con el client secret de la aplicación. Sin el client secret en las credenciales, HubSpot no envía cambios a Postal. |
| Pipedrive | Autenticación HTTP básica en el webhook: usuario postal y contraseña igual al webhook_secret de la conexión. |
| RD Station CRM | RD Station CRM no firma los webhooks: la URL lleva ?token=<webhook_secret> (o el encabezado X-Postal-Token). El token en la URL es la protección; no la compartas. |
Una firma o credencial incorrecta responde 401 invalid_signature. Si la seguridad del secreto se ve comprometida, genera otro con POST /crm-connections/{id}/webhook-secret/rotate y actualiza el webhook en el CRM: el antiguo deja de valer al instante.
HubSpot
- Credenciales:
access_token(obligatorio) yclient_secret(opcional, solo para webhooks). En HubSpot, ve a Configuración › Integraciones › Aplicaciones privadas y crea una aplicación con permiso de contactos (lectura y escritura), negocios (lectura) y esquemas de contactos y de negocios (lectura); copia el token de acceso. El client secret es el de la aplicación que firma las notificaciones. - Webhook: en la aplicación, suscríbete a contactos (creación y cambio de propiedad) y a negocios (creación y cambio de etapa) con la URL de la conexión. Las eliminaciones y las fusiones se ignoran.
- Campos: el registro es el de HubSpot,
{"id", "properties": {…}}, así que las rutas son$.properties.<nome interno>. Postal lee las propiedades que mapeas, además de correo, nombre, apellido y teléfono. Para negocios,$.deal.stage,$.deal.pipeline,$.deal.status,$.deal.titley$.deal.value. - Envío: el contacto se actualiza por el vínculo, o se busca por el correo y se crea si no existe. La actividad de correo se convierte en una nota asociada al contacto.
Pipedrive
- Credenciales:
api_tokenycompany_domain. En Pipedrive, abre el menú de la cuenta › Preferencias personales › API y copia el token personal. La dirección de la cuenta es solo la primera parte de la dirección que usas en el navegador:minhaempresaenminhaempresa.pipedrive.com(letras minúsculas, números y guiones). - Webhook: en Herramientas y apps › Webhooks, crea un webhook para personas y otro para negocios (todos los eventos), con la URL de la conexión y la autenticación HTTP básica indicada arriba. Las eliminaciones se ignoran. Un cambio en un negocio lee a su persona y los nombres de la etapa y del embudo.
- Campos:
$.email,$.first_name,$.last_name,$.name,$.phone,$.organization,$.id, los campos personalizados de la persona en$.custom.<chave>y, para negocios,$.deal.stage,$.deal.pipeline,$.deal.status,$.deal.titley$.deal.value. - Envío: la persona se actualiza por el vínculo o se busca por el correo y se crea si no existe; el correo y el teléfono nuevos entran como principales sin borrar los otros. La actividad de correo se convierte en una nota en la persona.
RD Station CRM
- Credenciales:
token, el token de la instancia. En RD Station CRM, abre el menú de la cuenta › Configuración › Integraciones › Token de la API. - Webhook: crea un webhook para contactos y otro para negociaciones con la URL de la conexión incluyendo el
?token=(la pantalla de la conexión muestra la URL lista). Las eliminaciones se ignoran. - Campos:
$.email,$.first_name,$.last_name,$.name,$.phone,$.title,$.organization,$.id, los campos personalizados del contacto en$.custom.<id>y, para negociaciones,$.deal.stage,$.deal.pipeline,$.deal.status,$.deal.titley$.deal.value. - Envío: el contacto se actualiza por el vínculo o se busca por el correo y se crea si no existe. La actividad de correo se convierte en una actividad en la negociación abierta del contacto; sin negociación, una línea en las anotaciones del contacto (las más antiguas salen cuando superan los 4.000 caracteres).
Permisos del token
Si el CRM rechaza el token o un permiso (HTTP 401 o 403), el registro muestra el mensaje y la conexión queda con el aviso de error hasta que un envío funcione. Comprueba el token y los permisos y usa Probar conexión.
#Registro de sincronización
Cada webhook recibido, persona sincronizada, envío al CRM, prueba y sincronización completa se convierte en una entrada del registro, en Integraciones › CRM (en la conexión) y en GET /crm-connections/{id}/logs. Una entrada tiene:
| Campo | Valores |
|---|---|
direction | inbound (del CRM) o outbound (hacia el CRM). Filtro: ?direction=. |
object_type | webhook (la entrega recibida), person, activity (actividad de correo electrónico), test o sync. |
status | pending (en cola o esperando un nuevo intento), success, failed o skipped (no había nada que hacer: contacto suprimido, sin coincidencia, conexión pausada). Filtro: ?status=. |
action | Qué ocurrió: created, updated, unchanged, upserted, activity, deleted o un resumen (“2 personas”). |
error, attempts, next_attempt_at | El mensaje, cuántos intentos ha habido ya y cuándo será el siguiente. |
external_id | El ID del registro en el CRM, cuando se conoce. |
retryable, payload | Si se puede reenviar, y lo que se recibió o envió (cortado a 16 KB; un payload cortado no se puede reenviar). |
Los mensajes del registro están escritos en portugués de Brasil.
{
"object": "list",
"has_more": false,
"data": [
{
"object": "crm_sync_log",
"id": "019a2c10-5e6f-7a81-9b2c-3d4e5f6a7b8c",
"direction": "outbound",
"object_type": "person",
"event": "contact.updated",
"external_id": "51",
"contact_id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"action": null,
"status": "failed",
"error": "HubSpot recusou as credenciais ou a permissão (HTTP 403). Confira o token e as permissões dele.",
"attempts": 6,
"retryable": true,
"payload": {
"id": "019a1e00-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"email": "[email protected]",
"first_name": "Ana"
},
"next_attempt_at": null,
"created_at": "2026-10-26T14:05:10.000000Z",
"updated_at": "2026-10-26T14:05:11.000000Z"
}
]
}
Nuevos intentos
- Los envíos al CRM que fallan se reintentan solos, tras 30 segundos, 2 minutos, 10 minutos, 30 minutos y 2 horas (6 intentos en total). Agotados los intentos, la entrada pasa a
failedy la conexión muestra el avisoerror(last_error); sigue funcionando y vuelve aactivecon el primer envío que salga bien. Si el CRM respondió429, Postal espera al menos lo que pidió. - Los webhooks recibidos se responden al instante con
202y se procesan en segundo plano. Si no se pudo consultar el CRM (límite de solicitudes, fuera de servicio), Postal lo intenta de nuevo, hasta 3 veces. - Una entrada
failedcon el payload completo se reenvía a mano, en el panel o conPOST /crm-connections/{id}/logs/{log_id}/retry, después de corregir la causa (un correo electrónico que faltaba, una propiedad que no existía, el token).
#Por la API
Todo lo que hace el panel está en la API, con una clave de acceso total (una clave solo de envío recibe 401 restricted_api_key). Los IDs son UUID, y las listas siguen la paginación por cursor.
| Endpoint | Qué hace |
|---|---|
GET /crm/providers | Los conectores (generic, pipedrive, rdstation_crm, hubspot y las fuentes de apps), el límite del plan y el uso de cada contador, y la plataforma asociada que gestiona el equipo, si la hay. |
GET /crm-connections | Lista las conexiones. ?limit_key=crm_connections (o app_integrations) filtra por contador. |
POST /crm-connections | Crea una conexión. Cuerpo: provider, name, credentials y settings. |
GET /crm-connections/{id} | Una conexión, con el webhook_secret. |
PATCH /crm-connections/{id} | Cambia name, status (active o paused), credentials (sustituyen a todas: envía todas las obligatorias) y settings (sustituyen a las anteriores: envía el conjunto completo). |
DELETE /crm-connections/{id} | Elimina la conexión y su registro. El CRM deja de sincronizarse; los contactos se quedan. |
POST /crm-connections/{id}/test | Comprueba el acceso al CRM (en los nativos, una llamada ligera; en el webhook, un postal.test). Guarda una entrada test en el registro. |
POST /crm-connections/{id}/preview | Muestra qué cambiaría un payload de ejemplo (payload, más settings opcionales para probar sin guardar), sin guardar nada. Pensado para la conexión por webhook. |
GET /crm-connections/{id}/fields | Los campos del CRM para el mapeo (key, label, type). En los nativos, vienen del CRM. |
GET /crm-connections/{id}/stages | Las etapas de cada embudo (pipeline, stage y status cuando el CRM lo indica). Vacío en los conectores sin embudo. |
POST /crm-connections/{id}/sync | Pone en cola la sincronización (202). 422 si la conexión está pausada o la dirección no lo permite. |
POST /crm-connections/{id}/webhook-secret/rotate | Genera un secreto nuevo; el anterior deja de valer al instante. |
GET /crm-connections/{id}/logs | El registro. Filtros: status y direction. |
POST /crm-connections/{id}/logs/{log_id}/retry | Reenvía una entrada failed con el payload completo. |
cURL
curl -X POST https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/test \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/test', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/test",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/test', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "crm_connection_test",
"ok": true,
"message": "Connected to HubSpot (12 contacts).",
"status_code": 200
}
Cuando el CRM rechaza la llamada, ok es false y message trae el motivo. fields, stages y la sincronización que no logran comunicarse con el CRM responden 502 crm_request_failed con el mensaje legible.
cURL
curl -X POST https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/sync \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/sync', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.post(
"https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/sync",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->post('crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/sync', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
]);
$data = json_decode($response->getBody(), true);
{
"object": "crm_connection",
"id": "019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b",
"sync": "queued"
}
cURL
curl 'https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/logs?status=failed' \
-H "Authorization: Bearer $POSTAL_API_KEY"
Node.js
const response = await fetch('https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/logs?status=failed', {
headers: {
Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
},
});
const data = await response.json();
Python
import os
import requests
response = requests.get(
"https://api.postal.so/crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/logs",
headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
params={
"status": "failed",
},
timeout=10,
)
data = response.json()
PHP
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);
$response = $client->get('crm-connections/019a2b3c-4d5e-7f60-8a9b-0c1d2e3f4a5b/logs', [
'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
'query' => [
'status' => 'failed',
],
]);
$data = json_decode($response->getBody(), true);
#Límites
| Qué | Límite |
|---|---|
| Conexiones | crm_connections del plan (y app_integrations para las de apps) |
| Cuerpo de un webhook recibido | 256 KB (413 payload_too_large) |
| Personas por webhook recibido | 500 (las que superan ese número se ignoran) |
| Webhooks recibidos | 600 por minuto, por IP de origen |
| Mapeos de campos / de etapas | 100 / 100 |
Orígenes que se ignoran (skip_sources) | 20 |
Ejemplo de payload guardado (sample_payload) | 32 KB |
| Tiempo límite de las llamadas al CRM | 10 segundos, sin seguir redirecciones |
| Payload guardado en el registro | 16 KB (los webhooks recibidos se guardan completos, hasta 256 KB) |
| Páginas leídas del CRM en una sincronización | 1.000 (de 100 a 200 personas cada una) |
#Errores
| Estado | Nombre | Cuándo ocurre |
|---|---|---|
401 | invalid_signature | La firma o el token del webhook falta o es incorrecto, o la fecha de la firma superó los 5 minutos. |
401 | restricted_api_key | Una clave solo de envío intentó usar /crm-connections. |
403 | plan_limit_reached | El plan no incluye conexiones de CRM o ya las usó todas. |
404 | not_found | La conexión no existe en este equipo, o el ID del webhook es de otra conexión. |
409 | connection_paused | La conexión está pausada, o solo envía (no recibe del CRM). |
413 | payload_too_large | El cuerpo del webhook supera los 256 KB. |
422 | validation_error | Credenciales o configuración no válidas: dirección desconocida, campo de Postal o ruta no válidos, propiedad que no existe, evento desconocido, URL que no es HTTPS, cuerpo del webhook que no es JSON; o un reenvío de una entrada que no es elegible. |
502 | crm_request_failed | El CRM rechazó la llamada de Postal o no respondió (campos, etapas y sincronización). |
El formato de los errores y los demás nombres están en Errores.
#Ver también
- Conexión por webhook: para cualquier otro CRM o sistema.
- Integra tu app: Stripe, Supabase, Clerk, Auth0 y Firebase, que usan las mismas conexiones.
- Webhooks: los eventos y la firma que la conexión reutiliza, y el origen de los cambios.
- Contactos, propiedades y automatizaciones.
- Servidor MCP: un asistente de IA también puede consultar y ajustar los contactos que el CRM sincroniza.