Guía de implementación
PQRS
Radica, consulta, responde y gestiona las peticiones, quejas, reclamos y sugerencias de tus afiliados desde tu CRM o call center, con plazos de ley, historial auditado y eventos pqrs.*.
En esta página
Con este módulo tu CRM o tu call center gestiona las PQRS (peticiones, quejas, reclamos y
sugerencias) de tus afiliados server-to-server, sin entrar al panel de Compense: radicas a nombre
del afiliado, listas y filtras (incluidas las vencidas), consultas el detalle con su historial,
respondes como la entidad y cambias estado o prioridad. Las reglas son exactamente las mismas de la
app y del panel: radicado PQRS-AAAA-NNNNNN, plazo legal, máquina de estados y notificación al
afiliado cuando se le responde.
Los ejemplos usan a Caja Horizonte, cuyo call center registra los casos en su CRM con referencias
HORIZONTE-CRM-….
Antes de empezar #
Scopes #
| Scope | Permite |
|---|---|
pqrs:read | Listar, ver el detalle con historial y estadísticas |
pqrs:submit | Radicar PQRS a nombre de un afiliado |
pqrs:manage | Responder y cambiar estado o prioridad como la entidad |
Pide solo lo que tu integración necesita: un call center que solo radica no necesita pqrs:manage.
pqrs:manage es un scope de alto valor (equivale a firmar una respuesta oficial) y está en la lista
de scopes que exigirán firma de la petición cuando se active esa protección en tu llave.
La PQRS es obligatoria para las entidades vigiladas, así que hoy está disponible para todas. Si en
algún momento tu entidad no tuviera el módulo, el gateway responde 403 con «El módulo PQRS no está
contratado para esta entidad.».
Identificadores #
Cada PQRS tiene un id (UUID) y un radicado legible (PQRS-2026-000123). Todas las rutas con
{pqrs_id} aceptan cualquiera de los dos. Guarda ambos en tu CRM junto a tu propia referencia.
| Operación | Ruta | Scope |
|---|---|---|
| Radicar | POST/v1/gateway/pqrs/ | pqrs:submit |
| Listar | GET/v1/gateway/pqrs/ | pqrs:read |
| Estadísticas | GET/v1/gateway/pqrs/stats/ | pqrs:read |
| Detalle | GET/v1/gateway/pqrs/{pqrs_id}/ | pqrs:read |
| Responder | POST/v1/gateway/pqrs/{pqrs_id}/respond/ | pqrs:manage |
| Cambiar estado / prioridad | POST/v1/gateway/pqrs/{pqrs_id}/status/ | pqrs:manage |
Las escrituras devuelven el sobre estándar del gateway; data trae la PQRS completa, con su
historial. Envía siempre Idempotency-Key: si reintentas con la misma llave, el gateway devuelve el
resultado original (idempotent_replay: true) en vez de radicar o responder dos veces.
Estados y plazos de ley #
stateDiagram-v2
[*] --> opened: radicar
opened --> in_progress
opened --> escalated
in_progress --> escalated
escalated --> in_progress
opened --> resolved: respuesta final
in_progress --> resolved: respuesta final
escalated --> resolved: respuesta final
opened --> closed
in_progress --> closed
escalated --> closed
resolved --> closed
closed --> [*]opened,in_progressyescalatedadmiten respuestas. Una respuesta conis_final: truedeja la PQRS enresolved.resolvedsolo puede pasar aclosed;closedes terminal.- Repetir el estado actual es un no-op: no crea historial ni webhook.
- Prioridades:
low,normal(por defecto),high,urgent.
El plazo se calcula al radicar: 15 días hábiles según la Ley 1755 de 2015, sin contar
sábados, domingos ni festivos. Cada PQRS trae deadline (fecha límite) e is_overdue, que pasa a
true cuando el plazo se superó sin resolverla. Úsalos para tus alertas internas; el filtro
overdue=true te da directamente las vencidas.
Radicar en nombre del afiliado #
Tu agente atiende la llamada, identifica al afiliado por su documento y radica el caso. El afiliado
debe existir en la base sincronizada de tu entidad (si no, 422).
import os
from compense_gateway import CompenseClient
client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")
tx = client.pqrs.submit(
document="1000000001",
issue_type="claim",
service_area="wallet",
subject="Cobro no reconocido",
description="El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.",
electronic_notification_consented=True, # lo que respondió el afiliado
origin_channel="call_center",
external_reference="HORIZONTE-CRM-88231",
agent="ana.p",
idempotency_key="horizonte-crm-88231-radicar",
)
pqrs = tx.data
print(pqrs["radicado"], pqrs["id"], pqrs["status"], pqrs["deadline"])import { CompenseClient } from "@compense/sdk";
const client = new CompenseClient({ apiKey: process.env.COMPENSE_API_KEY!, environment: "sandbox" });
const tx = await client.pqrs.submit({
document: "1000000001",
issueType: "claim",
serviceArea: "wallet",
subject: "Cobro no reconocido",
description: "El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.",
electronicNotificationConsented: true, // lo que respondió el afiliado
originChannel: "call_center",
externalReference: "HORIZONTE-CRM-88231",
agent: "ana.p",
idempotencyKey: "horizonte-crm-88231-radicar",
});
console.log(tx.data.radicado, tx.data.id, tx.data.status, tx.data.deadline);<?php
require __DIR__ . '/vendor/autoload.php';
use Compense\Sdk\CompenseClient;
$client = new CompenseClient(apiKey: getenv('COMPENSE_API_KEY'), environment: 'sandbox');
$tx = $client->pqrs->submit(
document: '1000000001',
issueType: 'claim',
serviceArea: 'wallet',
subject: 'Cobro no reconocido',
description: 'El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.',
electronicNotificationConsented: true, // lo que respondió el afiliado
originChannel: 'call_center',
externalReference: 'HORIZONTE-CRM-88231',
agent: 'ana.p',
idempotencyKey: 'horizonte-crm-88231-radicar',
);
echo $tx->data['radicado'], ' ', $tx->data['status'], ' ', $tx->data['deadline'], PHP_EOL;using Compense.Sdk;
using Compense.Sdk.Models;
using var client = new CompenseClient(
Environment.GetEnvironmentVariable("COMPENSE_API_KEY")!, CompenseEnvironment.Sandbox);
Transaction tx = await client.Pqrs.SubmitAsync(
document: "1000000001",
issueType: "claim",
serviceArea: "wallet",
subject: "Cobro no reconocido",
description: "El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.",
electronicNotificationConsented: true, // lo que respondió el afiliado
originChannel: "call_center",
externalReference: "HORIZONTE-CRM-88231",
agent: "ana.p",
idempotencyKey: "horizonte-crm-88231-radicar");
Console.WriteLine($"{tx.Data["radicado"]} {tx.Data["status"]} {tx.Data["deadline"]}");package main
import (
"context"
"fmt"
"log"
"os"
compense "github.com/Appiasoft/compense-saas-sdk-go"
)
func main() {
ctx := context.Background()
client, err := compense.NewClient(os.Getenv("COMPENSE_API_KEY"),
compense.WithEnvironment(compense.Sandbox))
if err != nil {
log.Fatal(err)
}
tx, err := client.PQRS.Submit(ctx, &compense.PQRSSubmitParams{
Document: "1000000001",
IssueType: "claim",
ServiceArea: "wallet",
Subject: "Cobro no reconocido",
Description: "El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.",
ElectronicNotificationConsented: true, // lo que respondió el afiliado
OriginChannel: "call_center",
ExternalReference: "HORIZONTE-CRM-88231",
Agent: "ana.p",
}, compense.WithIdempotencyKey("horizonte-crm-88231-radicar"))
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Data["radicado"], tx.Data["status"], tx.Data["deadline"])
}curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-crm-88231-radicar" \
-H "Content-Type: application/json" \
-d '{
"document": "1000000001",
"issue_type": "claim",
"service_area": "wallet",
"subject": "Cobro no reconocido",
"description": "El afiliado reporta un cobro de 45.000 que no reconoce en la billetera.",
"electronic_notification_consented": true,
"origin_channel": "call_center",
"external_reference": "HORIZONTE-CRM-88231",
"agent": "ana.p"
}'| Campo | Obligatorio | Valores |
|---|---|---|
document | sí | Documento del afiliado (debe existir en tu base sincronizada) |
issue_type | sí | petition · complaint · claim · suggestion |
service_area | sí | subsidies · credits · health · marketplace · benefits · wallet · payments · general |
subject | sí | Hasta 255 caracteres |
description | sí | Texto libre |
electronic_notification_consented | sí | true / false |
origin_channel | no (por defecto api) | call_center · crm · phone · email · in_person · chat · whatsapp · web · api · other |
external_reference | no | Id del caso en tu CRM (hasta 100). Sirve para filtrar y viaja en los webhooks |
agent | no | Referencia de tu agente, para trazabilidad (no pongas correos) |
related_type + related_*_id | no | Correlación con una solicitud o producto del mismo afiliado |
Consentimiento de notificación electrónica. La notificación electrónica exige aceptación expresa
del afiliado (Ley 1437 de 2011, art. 56), así que por API no se asume: el campo es obligatorio y
debe reflejar lo que el afiliado respondió en la llamada: si no aceptó, envía false (no lo
omitas ni lo asumas en true).
Canal de origen. app está reservado a la app del afiliado. Elige el canal real por el que
llegó el caso: es lo que verá el back-office y lo que alimenta las estadísticas por canal.
Correlación. Para atar el caso a un producto, envía related_type (credit_application,
subsidy_application, credit o subsidy) y el id correspondiente
(related_credit_application_id, related_subsidy_application_id, related_credit_id o
related_subsidy_active_id). En los SDK van como campos adicionales: extra en Python, JavaScript,
PHP y .NET; Extra en Go. Un id que no sea del mismo afiliado responde 422.
Respuesta (data, resumida):
{
"id": "4f0c…",
"radicado": "PQRS-2026-000123",
"issue_type": "claim", "service_area": "wallet",
"subject": "Cobro no reconocido",
"status": "opened", "priority": "normal",
"origin_channel": "call_center", "external_reference": "HORIZONTE-CRM-88231",
"electronic_notification_consented": true,
"deadline": "2026-11-24T14:03:11-05:00", "is_overdue": false,
"affiliate": {"document_number": "1000000001", "name": "S*** E***"},
"description": "…", "responses": [], "attachments": [],
"history": [
{"from_status": null, "to_status": "opened", "at": "…", "source": "gateway",
"actor": "integration:<id de tu llave> agent:ana.p", "note": "Radicación"}
]
}El nombre del afiliado va enmascarado y su correo nunca se devuelve.
Listar y filtrar #
GET/v1/gateway/pqrs/
pagina por número de página (page, page_size hasta 100) y devuelve un
Page con count y results, de la más reciente a la más antigua. Los resultados no traen
description ni historial: para eso está el detalle.
Filtros, todos opcionales: status, issue_type, service_area, origin_channel, document,
external_reference, date_from y date_to (AAAA-MM-DD, inclusivos, sobre la fecha de
radicación) y overdue=true para las vencidas (plazo superado y sin resolver). Un document
que no existe devuelve la lista vacía.
vencidas = client.pqrs.list(status="opened", overdue=True, page_size=50)
print(vencidas.count)
for p in vencidas.results:
print(p["radicado"], p["service_area"], p["deadline"], p["external_reference"])
# Buscar el caso por la referencia de tu CRM
caso = client.pqrs.list(external_reference="HORIZONTE-CRM-88231").resultsconst vencidas = await client.pqrs.list({ status: "opened", overdue: true, pageSize: 50 });
console.log(vencidas.count);
// `for await` recorre todas las páginas
for await (const p of vencidas) {
console.log(p.radicado, p.service_area, p.deadline, p.external_reference);
}
const caso = (await client.pqrs.list({ externalReference: "HORIZONTE-CRM-88231" })).results;$vencidas = $client->pqrs->list(status: 'opened', overdue: true, pageSize: 50);
echo $vencidas->count, PHP_EOL;
foreach ($vencidas->results as $p) {
echo $p['radicado'], ' ', $p['service_area'], ' ', $p['deadline'], PHP_EOL;
}
$caso = $client->pqrs->list(externalReference: 'HORIZONTE-CRM-88231')->results;using System.Text.Json.Nodes;
// Todas las vencidas, página por página
await foreach (JsonObject p in client.Pqrs.ListAutoPagingAsync(status: "opened", overdue: true, pageSize: 50))
{
Console.WriteLine($"{p["radicado"]} {p["service_area"]} {p["deadline"]}");
}
Page caso = await client.Pqrs.ListAsync(externalReference: "HORIZONTE-CRM-88231");it := compense.NewPageIterator(ctx, func(ctx context.Context, page int) (*compense.Page, error) {
return client.PQRS.List(ctx, &compense.PQRSListParams{
Status: "opened", Overdue: compense.Bool(true), Page: page, PageSize: 50,
})
})
for it.Next() {
p := it.Item()
fmt.Println(p["radicado"], p["service_area"], p["deadline"])
}
if err := it.Err(); err != nil {
log.Fatal(err)
}
caso, err := client.PQRS.List(ctx, &compense.PQRSListParams{ExternalReference: "HORIZONTE-CRM-88231"})
if err != nil {
log.Fatal(err)
}
fmt.Println(caso.Count)curl -G https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
--data-urlencode "status=opened" \
--data-urlencode "overdue=true" \
--data-urlencode "page_size=50"{"count": 37, "page": 1, "page_size": 50, "results": [ { "id": "…", "radicado": "PQRS-2026-000123", "status": "opened", "…": "…" } ]}Estadísticas #
GET/v1/gateway/pqrs/stats/
resume por estado, tipo y área, y cuenta las vencidas. Acepta
issue_type, service_area, date_from y date_to.
stats = client.pqrs.stats(date_from="2026-10-01", date_to="2026-10-31")
print(stats["total"], stats["overdue"], stats["by_status"], stats["by_service_area"])const stats = await client.pqrs.stats({ dateFrom: "2026-10-01", dateTo: "2026-10-31" });
console.log(stats.total, stats.overdue, stats.by_status, stats.by_service_area);$stats = $client->pqrs->stats(dateFrom: '2026-10-01', dateTo: '2026-10-31');
echo $stats['total'], ' vencidas: ', $stats['overdue'], PHP_EOL;JsonObject stats = await client.Pqrs.StatsAsync(dateFrom: "2026-10-01", dateTo: "2026-10-31");
Console.WriteLine($"{stats["total"]} vencidas: {stats["overdue"]}");stats, err := client.PQRS.Stats(ctx, &compense.PQRSStatsParams{DateFrom: "2026-10-01", DateTo: "2026-10-31"})
if err != nil {
log.Fatal(err)
}
fmt.Println(stats["total"], stats["overdue"], stats["by_status"])curl -G https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/stats/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
--data-urlencode "date_from=2026-10-01" \
--data-urlencode "date_to=2026-10-31"{"total": 37, "overdue": 2,
"by_status": {"opened": 20, "in_progress": 9, "resolved": 8},
"by_service_area": {"wallet": 15, "payments": 12, "general": 10},
"by_type": {"claim": 25, "petition": 12}}Detalle con historial #
El detalle incluye description, responses (texto, si es final, fecha y responder),
attachments e history: cada cambio de estado con from_status, to_status, at, source
(app, panel o gateway), actor y note. En el actor nunca van nombres ni correos de
funcionarios: staff para el panel, affiliate para la app e integration:<llave> agent:<ref> para
la API.
detalle = client.pqrs.get("PQRS-2026-000123") # o el id (UUID)
for h in detalle["history"]:
print(h["at"], h["from_status"], "→", h["to_status"], h["source"], h["actor"])const detalle = await client.pqrs.get("PQRS-2026-000123"); // o el id (UUID)
for (const h of detalle.history as Array<Record<string, unknown>>) {
console.log(h.at, h.from_status, "→", h.to_status, h.source, h.actor);
}$detalle = $client->pqrs->get('PQRS-2026-000123'); // o el id (UUID)
foreach ($detalle['history'] as $h) {
echo $h['at'], ' ', $h['from_status'] ?? '-', ' → ', $h['to_status'], ' ', $h['source'], PHP_EOL;
}JsonObject detalle = await client.Pqrs.GetAsync("PQRS-2026-000123"); // o el id (UUID)
foreach (JsonNode? h in detalle["history"]!.AsArray())
{
Console.WriteLine($"{h!["at"]} {h["from_status"]} → {h["to_status"]} {h["source"]}");
}detalle, err := client.PQRS.Get(ctx, "PQRS-2026-000123") // o el id (UUID)
if err != nil {
log.Fatal(err)
}
fmt.Println(detalle["status"], detalle["history"])curl https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/PQRS-2026-000123/ \
-H "X-API-Key: $COMPENSE_API_KEY"Responder como la entidad #
Solo se responde mientras la PQRS está opened, in_progress o escalated; si no, 409 («No se
puede responder una solicitud resuelta.»). Con is_final: true la PQRS pasa a resolved y se
registra resolved_at. El afiliado recibe una notificación push con la respuesta.
tx = client.pqrs.respond(
"PQRS-2026-000123",
response_text="Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.",
is_final=True,
agent="ana.p",
idempotency_key="horizonte-crm-88231-resp-1",
)
print(tx.data["status"]) # resolvedconst tx = await client.pqrs.respond("PQRS-2026-000123", {
responseText: "Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.",
isFinal: true,
agent: "ana.p",
idempotencyKey: "horizonte-crm-88231-resp-1",
});
console.log(tx.data.status); // resolved
$tx = $client->pqrs->respond(
'PQRS-2026-000123',
responseText: 'Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.',
isFinal: true,
agent: 'ana.p',
idempotencyKey: 'horizonte-crm-88231-resp-1',
);
echo $tx->data['status'], PHP_EOL; // resolved
Transaction tx = await client.Pqrs.RespondAsync(
"PQRS-2026-000123",
"Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.",
isFinal: true, agent: "ana.p", idempotencyKey: "horizonte-crm-88231-resp-1");
Console.WriteLine(tx.Data["status"]); // resolvedtx, err := client.PQRS.Respond(ctx, "PQRS-2026-000123", &compense.PQRSRespondParams{
ResponseText: "Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.",
IsFinal: compense.Bool(true),
Agent: "ana.p",
}, compense.WithIdempotencyKey("horizonte-crm-88231-resp-1"))
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Data["status"]) // resolvedcurl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/PQRS-2026-000123/respond/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-crm-88231-resp-1" \
-H "Content-Type: application/json" \
-d '{"response_text": "Se reversó el cobro el 28 de octubre. Verás el reintegro en tu billetera.", "is_final": true, "agent": "ana.p"}'Una respuesta parcial (is_final: false u omitido) queda en responses sin cambiar el estado:
úsala para informar avances sin cerrar el caso.
Cambiar estado y prioridad #
Cuando el caso pasa a otra área o hay que escalarlo, cambia el estado y, si hace falta, la
prioridad. La note queda en el historial.
tx = client.pqrs.change_status(
"PQRS-2026-000123",
status="escalated",
priority="high",
note="Pasa al área de pagos",
agent="luis.r",
idempotency_key="horizonte-crm-88231-estado-1",
)const tx = await client.pqrs.changeStatus("PQRS-2026-000123", {
status: "escalated",
priority: "high",
note: "Pasa al área de pagos",
agent: "luis.r",
idempotencyKey: "horizonte-crm-88231-estado-1",
});$tx = $client->pqrs->changeStatus(
'PQRS-2026-000123',
status: 'escalated',
priority: 'high',
note: 'Pasa al área de pagos',
agent: 'luis.r',
idempotencyKey: 'horizonte-crm-88231-estado-1',
);Transaction tx = await client.Pqrs.ChangeStatusAsync(
"PQRS-2026-000123", "escalated", priority: "high", note: "Pasa al área de pagos",
agent: "luis.r", idempotencyKey: "horizonte-crm-88231-estado-1");tx, err := client.PQRS.ChangeStatus(ctx, "PQRS-2026-000123", &compense.PQRSStatusParams{
Status: "escalated", Priority: "high", Note: "Pasa al área de pagos", Agent: "luis.r",
}, compense.WithIdempotencyKey("horizonte-crm-88231-estado-1"))
if err != nil {
log.Fatal(err)
}
fmt.Println(tx.Data["status"], tx.Data["priority"])curl -X POST https://sandbox.dev.compense.co/gw/v1/gateway/pqrs/PQRS-2026-000123/status/ \
-H "X-API-Key: $COMPENSE_API_KEY" \
-H "Idempotency-Key: horizonte-crm-88231-estado-1" \
-H "Content-Type: application/json" \
-d '{"status": "escalated", "priority": "high", "note": "Pasa al área de pagos", "agent": "luis.r"}'Una transición inválida responde 409 («No se puede pasar de “cerrada” a “abierta”.»).
Errores frecuentes #
| HTTP | Código del motor | Cuándo | detail (ejemplo) |
|---|---|---|---|
401 | — | Falta X-API-Key | — |
403 | — | Llave sin el scope, o módulo no contratado | El módulo PQRS no está contratado para esta entidad. |
404 | PQRS_NOT_FOUND | PQRS inexistente o de otra entidad | PQRS no encontrada. |
409 | INVALID_STATE | Responder una PQRS resuelta o cerrada | No se puede responder una solicitud cerrada. |
409 | INVALID_TRANSITION | Transición de estado no permitida | No se puede pasar de “cerrada” a “abierta”. |
409 | — | La misma Idempotency-Key sigue en curso | A request with this Idempotency-Key is already being processed. |
422 | VALIDATION_ERROR | Campos faltantes o con valores fuera de catálogo | Datos inválidos — subject: Este campo es requerido. |
422 | AFFILIATE_NOT_FOUND | El afiliado no existe en tu entidad | No existe un afiliado con ese documento en la entidad. Sincronícelo primero. |
422 | — | related_* de otro afiliado | — |
429 | — | Límite por minuto o cuota mensual | ver Retry-After |
502 / 504 | — | El motor falló o no respondió | Reintenta con la misma llave y cita el trace_id |
detail trae el mensaje del motor en español. Para distinguir un 409 de negocio de un conflicto
de idempotencia, mira el type del problema (…/upstream-error frente a
…/idempotency-conflict); el patrón completo está en Errores.
Eventos #
Los eventos pqrs.* se emiten en la misma transacción del cambio y desde cualquier canal (app,
panel o API): así tu CRM se entera de las PQRS que el afiliado radica en la app y de las respuestas
que da tu back-office sin sondear.
PQRS #
| Evento | Desde | Cuándo se emite |
|---|---|---|
pqrs.created | 2026-10-27 | Se radicó una PQRS. |
pqrs.status_changed | 2026-10-27 | Cambió el estado de una PQRS (incluye pasar a resolved por una respuesta final). |
pqrs.responded | 2026-10-27 | La entidad registró una respuesta oficial. |
{
"event": "pqrs.responded",
"version": "1",
"tenant_id": "horizonte",
"occurred_at": "2026-10-29T16:20:00Z",
"resource": {
"pqrs_id": "4f0c…", "radicado": "PQRS-2026-000123",
"response_id": "9a1e…", "is_final": true, "status": "resolved",
"external_reference": "HORIZONTE-CRM-88231",
"source": "gateway", "actor": "integration:<llave> agent:ana.p"
}
}pqrs.createdtrae ademásissue_type,service_area,status,priority,origin_channel,affiliate_idydeadline;pqrs.status_changedtraefrom_status,to_statusypriority.- Por privacidad, el webhook no lleva el texto de la respuesta ni el documento: consulta el
detalle con
pqrs.get. - Si enviaste
Idempotency-Key, elX-Webhook-Iddel evento es estable entre reintentos: úsalo para deduplicar (Reintentos de webhooks). - Usa
external_referencepara encontrar el caso en tu CRM sin guardar el UUID.
Sandbox #
Con una llave de sandbox las respuestas son simuladas y deterministas:
| Caso | Resultado |
|---|---|
Radicar con document 1000000001 a 1000000004 | 201; el mismo cuerpo produce el mismo id y radicado |
Radicar con document 9999999999 | 422 AFFILIATE_NOT_FOUND |
Responder o cambiar estado de PQRS-2026-999999 | PQRS cerrada: responder → 409; pasar a otro estado → 409; closed → 200 (no-op) |
Id que no es pqr-… ni un radicado | 404 |
| Listar / estadísticas | Bandeja fija de 5 PQRS de los documentos de prueba (aplica filtros y paginación) |
El sandbox no guarda estado: una escritura devuelve el estado nuevo, pero un GET posterior del
mismo id vuelve al estado inicial (opened). Los webhooks pqrs.* no se emiten en sandbox; para
probar tu receptor usa un evento de prueba.