Envíos

La API de Envíos contiene todos los endpoints y webhooks necesarios para crear un envío, obtener información sobre un envío y mantenerse actualizado sobre los eventos que afectan a un envío.

WebHooks

Necesitarás un endpoint público para que podamos comunicarte las novedades de los envíos en tiempo real.

Consideraciones

Tu endpoint deberá terminar en /shipping/updates

Ahí te notificaremos si hay actualizaciones sobre un envío. Serás responsable de buscar el recurso para obtener el estado actualizado.

Url de ejemplo

POST https://api.current-customer.com/whatever/shipping/updates

Body

{
  'shipment_id': 'shi-20VphpUWbbGm0Nwo8DMqsFBJgYv',
  'meta': {
    'resource_url': 'http://localhost:8080/v1/{shipment_id}'
  }
}

Ejemplo en Curl

curl --request POST \
--url https://api.current-customer.com/whatever/shipping/updates \
--header 'Content-Type: application/json' \
--data '{
    'shipment_id' : 'shi-20VphpUWbbGm0Nwo8DMqsFBJgYv',
    'meta' : {
        'resource_url' : 'http://localhost:8080/v1/shi-20VphpUWbbGm0Nwo8DMqsFBJgYv'
    }
}'

Seguridad

Como medida de seguridad, todas las notificaciones de novedades incluirán una firma hmac-sha256, pero no es necesario que la respuesta sea firmada por los clientes.

Firma hmac-sha256 del pedido

Para asegurarnos de que los únicos participantes en la comunicación sean nuestros backends (Pomelo y el Cliente), en el momento del onboarding le proporcionaremos una api-key y api-secret para firmar digitalmente el contenido de la comunicación.

A continuación explicamos con más profundidad los procesos de Intercambio de llaves y firma del pedido.

Proceso de Intercambio de llaves

Durante el proceso de onboarding, crearemos una api-key y api-secret específicas para ti.

También es posible usar gpg o algún plugin de correo electrónico (como por ejemplo https://flowcrypt.com/).

Veamos un ejemplo usando openssl en la línea de comandos.

  1. Creamos las api-key y api-secret:
$ echo -e 'api-key=$(openssl rand -base64 32)\napi-secret=$(openssl rand -base64 32)' > api-credentials.txt

$ cat api-credentials.txt
api-key=tgeAkX0795jKTxrVR0cJbb//D8UlhHn0KZwTcDG3gyg=
api-secret=un/OHwD+fMN1TTSaEhs0vupQEDQS7DVaUdlNOu7Fpyw=
  1. Creas tu clave pública-privada:
$ openssl genrsa -out private.pem 2048
$ openssl rsa -in private.pem -pubout -out public.pem
  1. Nos envías tu clave pública public.pem por email o Slack.

  2. Encriptamos el archivo de credenciales con la clave pública que nos diste:

$ openssl rsautl -encrypt -in api-credentials.txt -out api-credentials.txt.enc -inkey public.pem -pubin
  1. Enviamos el archivo api-credentials.txt.enc por email o Slack

  2. Desencriptas el archivo api-credentials.txt.enc con tu clave privada private.pem:

$ openssl rsautl -decrypt -in api-credentials.txt.enc -inkey private.pem
api-key=tgeAkX0795jKTxrVR0cJbb//D8UlhHn0KZwTcDG3gyg=
api-secret=un/OHwD+fMN1TTSaEhs0vupQEDQS7DVaUdlNOu7Fpyw=
  1. Guardas la api-secret en un lugar seguro y que sea accesible únicamente por tu aplicación, asociada al api-key.

Proceso de Firma del pedido

Junto con el pedido de autorización o ajuste te enviaremos headers HTTP con la firma, el timestamp de la firma y la api-key para que verifiques que la firma sea correcta.

Los headers HTTP que enviamos son:

  • x-api-key : este header te permitirá identificar qué api-secret tenés que usar en el caso que se hayan configurado múltiples pares de api-key y api-secret.

  • x-signature : este header contiene la firma digital (timestamp + endpoint + body) que deberás verificar para asegurar la integridad del request. Si la firma no coincide, deberás rechazar el pedido.

  • x-timestamp : este header contiene el momento en el que se firmó el pedido en formato unix-epoch para que puedas corroborar que la firma no expiró.

  • x-endpoint : el endpoint al que se realiza el pedido y usaste para generar la firma. Usa este header para regenerar la firma a validar, compararlo con el endpoint de tu servicio y verificar que coinciden.

Veamos un curl de ejemplo indicando cómo se envía esta información dentro del request:

curl --location --request POST 'localhost:9090/shipping/updates' \
--header 'x-api-key: h3Ws4Cv09JcCdw7732ig+1Eq3I2b+IWOI1anUu1A4dE=' \
--header 'x-signature: hmac-sha256 kLV3Jeyn7qbKfGHLDQKKuy5xzG/kbPrYEg8RvD8jb8A=' \
--header 'x-timestamp: 1637117179' \
--header 'x-endpoint: {clientPath}/shipping/updates'
--header 'Content-Type: application/json' \
--data-raw '{
    'shipment_id' : 'shi-20VphpUWbbGm0Nwo8DMqsFBJgYv',
    'meta' : {
        'resource_url' : 'http://localhost:8080/v1/shi-20VphpUWbbGm0Nwo8DMqsFBJgYv'
    }
}'

Para el armado de la firma se puede utilizar el siguiente snippet de código (en python 3) como por ejemplo:

import hmac, hashlib, base64, time

############# Pomelo's side ################
############################################

# pomelo's secret key
pomelo_secret_key = base64.b64decode('kLV3Jeyn7qbKfGHLDQKKuy5xzG/kbPrYEg8RvD8jb8A=')

# the update data pomelo is gonna use to make the signature
pomelo_update_body = '{
    'shipment_id' : 'shi-20VphpUWbbGm0Nwo8DMqsFBJgYv',
    'meta' : {
        'resource_url' : 'http://localhost:8080/v1/shi-20VphpUWbbGm0Nwo8DMqsFBJgYv'
    }
}'
client_req_endpoint = '/shipping/updates'
pomelo_timestamp = str(int(time.time()))

# the request data signed out by pomelo
pomelo_signature = hmac.new(pomelo_secret_key, bytes(pomelo_timestamp + client_req_endpoint + pomelo_update_body, 'utf-8'), hashlib.sha256).digest()


############# Client's side ################
############################################

# client's secret key
client_secret_key = base64.b64decode('kLV3Jeyn7qbKfGHLDQKKuy5xzG/kbPrYEg8RvD8jb8A=')

# client's api endpoint
client_api_endpoint = '/transactions/authorizations'

# signature expiration offset
signature_expiration_offset = 60 #A minute of expiration

# the authorizacion data the client needs to use to recreate the signature
client_request_data = bytes(pomelo_timestamp + client_req_endpoint + pomelo_update_body, 'utf-8')

# the signature the client has recreated
signature = hmac.new(client_secret_key, client_request_data, hashlib.sha256).digest()

# if signature is not expired and they match you can trust the request, otherwise reject it
now = int(time.time()) - signature_expiration_offset

signatureExpired = int(pomelo_timestamp) < now
signaturesMatch = hmac.compare_digest(signature, pomelo_signature)
endpointsMatch = client_api_endpoint in client_req_endpoint

requestIsValid = not signatureExpired and signaturesMatch


################ Results ###################
############################################

print('pomelo' signature = {0}'.format(base64.b64encode(pomelo_signature)))
print('client's signature = {0}'.format(base64.b64encode(signature)))
print('signature expired = {0}'.format(signatureExpired))
print('signatures match = {0}'.format(signaturesMatch))
print('endpoints match = {0}'.format(endpointsMatch))
print('request is valid = {0}'.format(requestIsValid))

Envíos Externos

A la hora de definir tu integración, podrás optar por NO utilizar el servicio de envíos que ofrece Pomelo junto a sus socios.

En ese caso, los grupos de afinidad reflejarán la configuración que hayas elegido. Además, al momento de crear una tarjeta o un lote de tarjetas, te devolveremos un identificador en el campo shipment_id, que te recomendamos almacenar en tu integración ya que lo necesitarás para hacer el retiro. Las tarjetas estarán disponibles para su retiro en la planta embozadora definida en la integración.

Crear Envío

El endpoint /shipping/v1/ se usa para crear un nuevo envío.

Consideraciones

El campo region se corresponde con:

  • Provincia en Argentina
  • Estado en Brasil
  • Estado en Colombia
  • Estado en Mexico

El campo courier.tracking_url de la respuesta, estará disponible una vez que el envío sea confirmado por el partner logístico correspondiente. Hasta entonces, tendrá valor nulo

Para Brasil

  • Si operas en Brasil, el taxIdentificationNumber siempre será obligatorio.
  • Los campos documentNumber y documentType no serán requeridos.
  • Además, deberás completar el campo region con el código UF de dos caracteres. Ejemplo: 'SP' para São Paulo
    • Rondônia (RO)
    • Acre (AC)
    • Amazonas (AM)
    • Roraima (RR)
    • Pará (PA)
    • Amapá (AP)
    • Tocantins (TO)
    • Maranhão (MA)
    • Piauí (PI)
    • Ceará (CE)
    • Rio Grande do Norte (RN)
    • Paraíba (PB)
    • Pernambuco (PE)
    • Alagoas (AL)
    • Sergipe (SE)
    • Bahia (BA)
    • Minas Gerais (MG)
    • Espírito Santo (ES)
    • Rio de Janeiro (RJ)
    • São Paulo (SP)
    • Paraná (PR)
    • Santa Catarina (SC)
    • Rio Grande do Sul (RS)
    • Mato Grosso do Sul (MS)
    • Mato Grosso (MT)
    • Goiás (GO)
    • Distrito Federal (DF)

Para Colombia

Si operas en Colombia, el campo zip_code es opcional, es decir que podrás no enviarlo.

Parámetros disponibles
Header Parameters
Authorizationstringrequired
Ejemplo: Bearer {access_token}
x-idempotency-keystringrequired
ID único en cada request para usar nuestro esquema de idempotencia.
Ejemplo: fRwX12Dg3345AD
Body Parameters
shipment_typestringrequired
Enum: CARD_FROM_WAREHOUSE
affinity_group_idstringrequired
Ejemplo: afg-20MpN8vmIPj77ujhb9cS8ctstN2
countrystringrequired
Ejemplo: ARG
addressobjectrequired
receiverobjectrequired
Detalle de respuestas
dataobject

¿Te resultó útil esta sección?

POST/shipping/v1/
{
"shipment_type":
"CARD_FROM_WAREHOUSE"
"affinity_group_id":
"afg-20MpN8vmIPj77ujhb9cS8ctstN2"
"country":
"ARG"
"address":{
"street_name":
"Libertador"
"street_number":
"539"
"floor":
"2"
"apartment":
"B"
"city":
"Buenos Aires"
"region":
"Buenos Aires"
"country":
"Argentina"
"zip_code":
"5346"
"neighborhood":
"string"
}
"receiver":{
"full_name":
"Gonzalo Iglesias"
"document_type":
"dni"
"document_number":
"243432423"
"tax_identification_number":
"38912151888"
"telephone_number":
"53454342"
}
}
Ejemplo de respuestas
{
"data":{
"id":
"shi-23hJL4bm94q9BFEd2sGhBjY6xbH"
"external_tracking_id":
"f923da123"
"status":
"CREATED"
"status_detail":
"CREATED"
"shipment_type":
"SINGLE_CARD"
"affinity_group_id":
"afg-20MpN8vmIPj77ujhb9cS8ctstN2"
"affinity_group_name":
"Pomelo Nominate Basic Physical"
"courier":{
...
}
"country_code":
"ARG"
"created_at":
"2020-07-10 15:00:00.000"
"batch":{
...
}
"address":{
...
}
"receiver":{
...
}
}
}

Buscar Envios

El endpoint /shipping/v1/ te permite buscar un grupo de envios según los atributos que especifiques.

Consideraciones

Tendrás que especificar tus filtros como parámetros siguiendo este patrón: filter[campo]=valor. Por ejemplo: filter[shipment_type]=BOX. Para filtrar un atributo con varios posibles valores, deberás separar los valores por coma. Veamos un ejemplo: filter[shipment_type]=BOX,WAREHOUSE

Los resultados serán paginados y podrás especificar la cantidad de datos por página y también qué página ver utilizando: page[1] y page[2]

Ordenamiento

Podrás especificar el orden de los resultados con determinados parámetros que deberás enviar como una lista de strings en el filtro de tipo sort. Por ejemplo: /shipping/v1/?sort=status,shipment_type.

El ordenamiento por defecto será ascendente. Para especificar un orden descendente, deberás enviar el carácter '-' como prefijo del atributo. Por ejemplo: /shipping/v1/?sort=status,-shipment_type.

Los atributos para ordenar son:

  • shipment_type
  • status
  • status_detail
  • created_at

Si un parámetro es incorrecto o está mal escrito, responderemos con un error.

Parámetros disponibles
Header Parameters
Authorizationstringrequired
Ejemplo: Bearer {access_token}
Query Parameters
filter[shipment_type]string
Enum: SINGLE_CARDBOXCARD_FROM_WAREHOUSEWAREHOUSE_BOX
filter[batch_id]string
filter[email]string
filter[status]string
filter[batch_status]string
filter[document_number]string
filter[start_date]string
filter[end_date]string
filter[country_code]string
ISO 3166-1 alpha-3
page[size]number
Tamaño de página.
page[number]number
Número de página. El número de la primer página es 0.
sortstring
Ejemplo: pid, shipment_type, status, status_detail
Detalle de respuestas
idstring
Ejemplo: shi-23hJL4bm94q9BFEd2sGhBjY6xbH
external_tracking_idstring
Ejemplo: f923da123
statusstring
Enum: CREATEDPENDINGTRACKEDREJECTEDIN_WAREHOUSEIN_TRANSITFAILED_DELIVERY_ATTEMPTDISTRIBUTIONDELIVEREDNOT_DELIVEREDSTART_OF_CUSTODYEND_OF_CUSTODYDESTRUCTIONACCIDENT
status_detailstring
Enum: CREATEDPENDINGTRACKEDREADY_FOR_DISTRIBUTIONROAD_TO_PICKUPUNSUCCESSFUL_PICKUPROAD_TO_RETURNUNSUCCESSFUL_RETURNREJECTEDSHIPMENT_SENTSHIPMENT_CONFIRMEDIN_WAREHOUSEIN_TRANSITRECEIVED_BY_CARRIERFAILED_DELIVERY_ATTEMPTWRONG_ADDRESSSHIPMENT_CONSOLIDATEDDISTRIBUTIONDELIVEREDNOT_DELIVEREDSTART_OF_CUSTODYEND_OF_CUSTODYZONE_ASSIGNEDDESTRUCTIONACCIDENTSTOLENFAILED_TO_MATCH_GIVEN_TIMESLOTVISIT_SCHEDULEDCALL_SCHEDULEDVISIT_CONFIRMEDCALL_FAILED_UNKNOWNNO_ACCESSFAILED_VISIT_DEADWRONG_OPERATOR_ZONECALL_RESCHEDULEDCALL_FAILED_DEADCALL_FAILEDWHATSAPP_FAILEDCOURIER_FAILEDMISSING_DOCUMENTSRETURNED_TO_WAREHOUSECLIENT_NOT_PRESENTINCOMPLETE_ADDRESSCOVERAGEOPERATOR_REASSIGNEDCLIENT_UNKNOWNNO_CALLZONE_PROBLEMSCLIENT_WORKFRAUDFRAUD_CLIENTMISSING_NUMBERCLIENT_CANCELEDDESTROYED_BY_CLIENTWEATHERLOSTCLIENT_TRIPFAILED_VISIT_UNKNOWNRETURNED
shipment_typestring
Enum: SINGLE_CARDBOXWAREHOUSE_BOXCARD_FROM_WAREHOUSEEXTERNAL_SINGLE_CARDEXTERNAL_BOX
affinity_group_idstring
Ejemplo: afg-20MpN8vmIPj77ujhb9cS8ctstN2
affinity_group_namestring
Ejemplo: Pomelo Nominate Basic Physical
courierobject
country_codestring
Ejemplo: ARG
created_atstring
Ejemplo: 2020-07-10 15:00:00.000
batchobject
addressobject
receiverobject

¿Te resultó útil esta sección?

GET/shipping/v1/
Ejemplo de respuestas
{
"id":
"shi-23hJL4bm94q9BFEd2sGhBjY6xbH"
"external_tracking_id":
"f923da123"
"status":
"CREATED"
"status_detail":
"CREATED"
"shipment_type":
"SINGLE_CARD"
"affinity_group_id":
"afg-20MpN8vmIPj77ujhb9cS8ctstN2"
"affinity_group_name":
"Pomelo Nominate Basic Physical"
"courier":{
"company":
"ANDREANI"
"tracking_url":
"https://www.api.qa.com/#!/informacionEnv ..."
}
"country_code":
"ARG"
"created_at":
"2020-07-10 15:00:00.000"
"batch":{
"id":
"bch-23WEkAaZsqubbke9pYVAt9sG81e"
"quantity":
100
"has_stock":
true
"status":
"PENDING"
}
"address":{
"street_name":
"Libertador"
"street_number":
"539"
"floor":
"2"
"apartment":
"B"
"city":
"Buenos Aires"
"region":
"Buenos Aires"
"country":
"Argentina"
"zip_code":
"5346"
"neighborhood":
"string"
}
"receiver":{
"full_name":
"Gonzalo Iglesias"
"document_type":
"dni"
"document_number":
"243432423"
"tax_identification_number":
"38912151888"
"telephone_number":
"53454342"
}
}

Obtener Envío

El endpoint /shipping/v1/{shipment_id} te permite obtener información sobre un envío en particular.

Consideraciones

Deberás especificar el shipment_id para hacer la consulta.

Envíos hacia un warehouse y en territorio mexicano:

Para los envíos hacia un warehouse y también para los que se realizan en México, no devolveremos un ID externo de seguimiento, ¡pero descuida! Te mantendremos informado sobre el estado del envío desde el Dashboard y también vía webhooks.

Parámetros disponibles
Header Parameters
Authorizationstringrequired
Ejemplo: Bearer {access_token}
Path Parameters
shipment_idstringrequired
Id del envío
Detalle de respuestas
dataobject

¿Te resultó útil esta sección?

GET/shipping/v1/{shipment_id}
Ejemplo de respuestas
{
"data":{
"id":
"shi-23hJL4bm94q9BFEd2sGhBjY6xbH"
"external_tracking_id":
"f923da123"
"status":
"CREATED"
"status_detail":
"CREATED"
"shipment_type":
"SINGLE_CARD"
"affinity_group_id":
"afg-20MpN8vmIPj77ujhb9cS8ctstN2"
"affinity_group_name":
"Pomelo Nominate Basic Physical"
"courier":{
...
}
"country_code":
"ARG"
"created_at":
"2020-07-10 15:00:00.000"
"batch":{
...
}
"address":{
...
}
"receiver":{
...
}
}
}