Webhooks: Agregar/Actualizar Producto
Descripción General
El webhook Agregar/Actualizar Producto permite que Talk2sync cree nuevos productos o actualice los existentes en tu sistema. Talk2sync envía una solicitud POST con datos completos del producto que tu aplicación debe insertar o actualizar en tu base de datos.
Este endpoint implementa una operación upsert (insertar o actualizar) donde Talk2sync crea un producto nuevo o actualiza uno existente según el ID del producto.
Formato de Solicitud
Solicitud HTTP
POST https://www.example.com/your/endpoint/url?jobid=123&id=999999
T2SKey: {{API_KEY}}
Content-Type: application/json
{
"_id": "999999",
"sku": "999999",
"last_updated": 1517360038797,
"title": "EON618S JBL SUBWOOFER 18\" AMPLIFICADO",
"url": "",
"brand": "",
"mpn": "",
"model": "",
"description": "JBL Premium Transducers",
"variations": [
{
"availabilities": [
{
"tag": "default",
"quantity": 50
}
],
"prices": [
{
"tag": "default",
"currency": "USD",
"number": 1060.51
}
],
"images": [
{
"url": "https://www.example.com/13080-thickbox_default/eon618s-jbl-subwoofer-18-amplificado-.jpg"
}
],
"videos": [
{
"url": ""
}
],
"barcode": "",
"size": "",
"color": "",
"variationid": ""
}
],
"properties": [
{
"extraattributes": {
"promociones": "",
"status": "",
"condicion_venta": "",
"peso": "",
"relacionados": "",
"custom_01": "",
"custom_02": "1",
"custom_03": ""
}
}
]
}
Parámetros de Solicitud
| Parámetro | Tipo | Descripción | Obligatorio |
|---|---|---|---|
jobid | integer | Identificador único para este trabajo de sincronización (para seguimiento) | ✓ Sí |
id | string | ID del producto a insertar/actualizar | ✓ Sí |
T2SKey | header | Tu clave de seguridad | ✓ Sí |
Cuerpo de Solicitud
El cuerpo de la solicitud contiene el objeto de producto completo con todos los detalles. Consulta Listar Productos para una referencia completa de campos.
Respuesta de Éxito
Si el producto se crea o actualiza exitosamente, devuelve una respuesta 200 OK con la siguiente estructura JSON:
{
"success": true,
"code": 200,
"warnings": [],
"product": {
"_id": "100004777",
"sku": "100004777",
"last_updated": 1517360038797,
"title": "EON618S JBL SUBWOOFER 18\" AMPLIFICADO",
"url": "",
"brand": "",
"mpn": "",
"model": "",
"description": "JBL Premium Transducers",
"variations": [
{
"availabilities": [
{
"tag": "default",
"quantity": 50
}
],
"prices": [
{
"tag": "default",
"currency": "USD",
"number": 1060.51
}
],
"images": [
{
"url": "https://www.example.com/13080-thickbox_default/eon618s-jbl-subwoofer-18-amplificado-.jpg"
}
],
"videos": [
{
"url": ""
}
],
"barcode": "",
"size": "",
"variationid": ""
}
],
"properties": [
{
"extraattributes": {
"promociones": "",
"status": "",
"condicion_venta": "",
"peso": "",
"relacionados": "",
"custom_01": "",
"custom_02": "1",
"custom_03": ""
}
}
]
}
}
Campos de Respuesta de Éxito
| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | Siempre true para operaciones exitosas |
code | integer | Código de estado HTTP (p.ej., 200) |
warnings | array | Array de mensajes de advertencia (si hay) |
product | object | El objeto de producto creado/actualizado exitosamente |
Ejemplo de Advertencias
Puedes incluir advertencias no críticas si ciertos campos fueron ajustados:
{
"success": true,
"code": 200,
"warnings": [
{
"msg": "La URL de imagen era inválida y ha sido omitida"
},
{
"msg": "El precio fue ajustado para coincidir con la moneda de tu tienda"
}
],
"product": { ... }
}
Respuesta de Error
Si la operación falla, devuelve una respuesta de error con información detallada sobre qué salió mal:
{
"success": false,
"code": 400,
"error": "No se pudo crear/actualizar el producto",
"reasons": [
{
"msg": "Precio cero inválido"
},
{
"msg": "Título duplicado inválido"
},
{
"msg": "Imágenes inválidas"
}
],
"warnings": []
}
Campos de Respuesta de Error
| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | Siempre false para operaciones fallidas |
code | integer | Código de estado HTTP (p.ej., 400, 500) |
error | string | Mensaje de error de alto nivel |
reasons | array | Array de razones de error específicas |
warnings | array | Cualquier advertencia que ocurrió antes del fallo |
Escenarios de Error Comunes
Errores de Validación (400 Bad Request)
{
"success": false,
"code": 400,
"error": "La validación falló",
"reasons": [
{
"msg": "El título es obligatorio"
},
{
"msg": "El precio debe ser mayor a cero"
}
],
"warnings": []
}
Errores de Base de Datos (500 Internal Server Error)
{
"success": false,
"code": 500,
"error": "La operación de base de datos falló",
"reasons": [
{
"msg": "No se pudo conectar a la base de datos"
}
],
"warnings": []
}
Error de Autenticación (401 Unauthorized)
{
"success": false,
"code": 401,
"error": "No autorizado",
"reasons": [
{
"msg": "Clave de API inválida o faltante"
}
],
"warnings": []
}
Ejemplo de Implementación (Node.js/Express)
app.post('/api/products', (req, res) => {
const { jobid, id } = req.query;
const apiKey = req.headers['t2skey'];
const productData = req.body;
// Validar clave de API
if (apiKey !== process.env.TALK2SYNC_KEY) {
return res.status(401).json({
success: false,
code: 401,
error: 'No autorizado',
reasons: [{ msg: 'Clave de API inválida o faltante' }],
warnings: []
});
}
try {
// Validar datos del producto
const validationErrors = validateProduct(productData);
if (validationErrors.length > 0) {
return res.status(400).json({
success: false,
code: 400,
error: 'La validación falló',
reasons: validationErrors.map(msg => ({ msg })),
warnings: []
});
}
// Realizar operación upsert
const savedProduct = upsertProduct(id, productData);
// Devolver respuesta de éxito
res.json({
success: true,
code: 200,
warnings: [],
product: savedProduct
});
} catch (error) {
console.error('Error al crear/actualizar producto:', error);
res.status(500).json({
success: false,
code: 500,
error: 'La operación de base de datos falló',
reasons: [{ msg: error.message }],
warnings: []
});
}
});
// Función de validación
function validateProduct(product) {
const errors = [];
// Verificar campos obligatorios
if (!product.title || product.title.trim() === '') {
errors.push('El título es obligatorio');
}
// Validar variaciones
if (!product.variations || product.variations.length === 0) {
errors.push('Se requiere al menos una variación');
}
// Validar precios
product.variations?.forEach((variation, idx) => {
variation.prices?.forEach((price, priceIdx) => {
if (price.number <= 0) {
errors.push(`Variación ${idx}: El precio ${priceIdx} debe ser mayor a cero`);
}
});
});
// Validar imágenes
product.variations?.forEach((variation, idx) => {
variation.images?.forEach((image, imgIdx) => {
if (!isValidUrl(image.url)) {
errors.push(`Variación ${idx}: La imagen ${imgIdx} tiene URL inválida`);
}
});
});
return errors;
}
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
// Función upsert
function upsertProduct(id, productData) {
// Verificar si el producto existe
const existingProduct = findProductById(id);
if (existingProduct) {
// Actualizar producto existente
return updateProductInDatabase(id, productData);
} else {
// Insertar nuevo producto
return insertProductInDatabase(productData);
}
}
Códigos de Estado de Respuesta
| Estado | Significado | Respuesta | Acción |
|---|---|---|---|
200 OK | Producto creado/actualizado exitosamente | Respuesta de éxito con producto | Talk2sync registra la operación |
400 Bad Request | Error de validación en datos del producto | Respuesta de error con razones | Verifica los errores de validación |
401 Unauthorized | Clave de API inválida o faltante | Respuesta de error | Verifica tu clave de seguridad |
409 Conflict | El producto entra en conflicto con datos existentes | Respuesta de error con razones | Resuelve el conflicto |
500 Internal Server Error | Error del servidor | Respuesta de error con mensaje de error | Revisa los registros de tu servidor |
Buenas Prácticas
✓ Haz:
- Valida todos los campos obligatorios antes de inserción/actualización
- Devuelve mensajes de error específicos en el array
reasons - Incluye advertencias para problemas no críticos
- Siempre valida la clave de API
- Usa transacciones para operaciones de base de datos (si es compatible)
- Actualiza el timestamp de
last_updateda la hora actual - Registra todas las operaciones para depuración
✗ No hagas:
- Aceptes datos de producto parciales (requiere objetos completos)
- Omitas campos inválidos sin notificar
- Devuelvas mensajes de error vagos
- Modifiques datos que no fueron enviados por Talk2sync
- Actualices productos que no pertenecen a la cuenta/tienda actual
- Ignores errores de validación
Lógica de Upsert
La operación upsert debe funcionar de la siguiente manera:
SI el producto con ID existe en la base de datos
ENTONCES actualiza todos los campos con datos nuevos
EN CASO CONTRARIO
CREA un nuevo producto con datos proporcionados
FIN
Crear vs Actualizar
| Operación | Cuándo | Acción |
|---|---|---|
| CREATE (INSERT) | El ID del producto no existe | Insertar un nuevo registro de producto |
| UPDATE | El ID del producto ya existe | Reemplaza todos los campos con datos nuevos |
Consistencia de Datos
- Timestamps: Siempre usa el timestamp de
last_updatedde Talk2sync (no generes uno nuevo) - ID de Producto: El
_idy el parámetroiddeben coincidir - SKU: Almacena el SKU para referencia; debería coincidir con
_iden la mayoría de casos - Relaciones: Borra cualquier variación/propiedad anterior antes de actualizar
Idempotencia
Esta operación es idempotente, lo que significa que llamarla múltiples veces con los mismos datos debe producir el mismo resultado:
- Primera llamada: Crea/actualiza el producto
- Segunda llamada (mismos datos): Devuelve la misma respuesta de éxito
- Tercera llamada (mismos datos): Devuelve la misma respuesta de éxito
Esto asegura que si Talk2sync reintenta la operación, no se creen productos duplicados.
Límites de Tamaño de Solicitud/Respuesta
- Tamaño máximo de solicitud: 10 MB por producto
- La respuesta debe mantenerse bajo 5 MB
- Procesa solicitudes dentro de 1 minuto de timeout