Requisitos en Masnet
Antes de configurar el sistema receptor, prepare el destino en Masnet. Los pasos son los mismos para todos los sistemas.
- Abra Empresa › Integraciones, vaya a la pestaña Destinos y seleccione Nuevo destino. Si la integración aún no tiene datos autorizados, Masnet le pedirá autorizarlos primero.
- En Dirección del destino (https) escriba la URL pública del receptor. Debe ser
https, puerto 443 u 8443 y un nombre DNS público: no se admiten usuario en la URL, fragmento, direcciones IP ni nombres internos. Se admite una cadena de consulta; Masnet trata la URL completa como una credencial. - En Formato elija JSON estándar (
application/json) o CloudEvents estructurado (application/cloudevents+json). El contenido del evento es el mismo; consulte Webhooks. - Seleccione Crear destino. Masnet muestra el secreto de firma (
whsec_…) una sola vez. Guárdelo de inmediato en el almacén de secretos del receptor; no podrá consultarlo después. - Seleccione Probar destino y luego Enviar prueba. La integración solo se activa con una prueba exitosa; después use Activar destino.
Configuración del iFlow
- Cree un iFlow y use un adaptador de remitente HTTPS.
- En el parámetro de dirección escriba una ruta propia, por ejemplo
/masnet/eventos. La URL de ejecución tiene la formahttps://<host de ejecución>/http/masnet/eventos; esa URL completa es la que se escribe en Dirección del destino (https) en Masnet. - En el parámetro de autorización elija un método de SAP compatible con uno de los métodos que Masnet puede enviar (Basic, Bearer, OAuth 2.0 con credenciales de cliente o mTLS) y configure el mismo método en las credenciales del destino de Masnet. La firma se envía siempre, con cualquier método.
- Deje desactivada la protección CSRF: Masnet no obtiene un token previo. Confirme el comportamiento en la documentación de su versión.
- Agregue un paso de Script inmediatamente después del adaptador, antes de cualquier conversor o mapeo. Pegue el script de la sección siguiente.
- Cargue el secreto de firma como parámetro seguro de SAP y asígnelo a la propiedad
webhook_secretdel mensaje antes del script. No lo escriba dentro del script.
Verificación de la firma
La firma es v1, seguido de base64(HMAC-SHA256) calculado con la clave binaria que resulta de decodificar en base64 lo que sigue a whsec_, sobre los bytes webhook-id + . + webhook-timestamp + . + cuerpo exacto. Verifique siempre sobre los bytes recibidos, antes de interpretar el JSON.
El script lee webhook-id, webhook-timestamp y webhook-signature de las cabeceras del mensaje y el cuerpo como byte[]. Rechaza eventos con más de 5 minutos de diferencia y acepta cualquiera de las firmas del encabezado.
import com.sap.gateway.ip.core.customdev.util.Message
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
// Script step of the iFlow. The secret is read from a Secure Parameter alias or property, never hardcoded.
Message processData(Message message) {
def headers = message.getHeaders()
String id = headers.get('webhook-id')
String timestamp = headers.get('webhook-timestamp')
String signatures = headers.get('webhook-signature')
String secret = message.getProperty('webhook_secret')
byte[] body = message.getBody(byte[]) // raw bytes, before any JSON mapping
boolean ok = false
if (id && timestamp ==~ /\d+/ && signatures && Math.abs(System.currentTimeMillis().intdiv(1000) - Long.parseLong(timestamp)) <= 300) {
byte[] key = Base64.decoder.decode(secret.replaceFirst('^whsec_', ''))
Mac mac = Mac.getInstance('HmacSHA256')
mac.init(new SecretKeySpec(key, 'HmacSHA256'))
mac.update("${id}.${timestamp}.".getBytes('UTF-8'))
byte[] expected = mac.doFinal(body)
// Two signatures separated by a space while the secret is rotating; any match is valid.
ok = signatures.split(' ').any { part ->
def pair = part.split(',', 2)
if (pair.length != 2 || pair[0] != 'v1') return false
try {
return MessageDigest.isEqual(Base64.decoder.decode(pair[1]), expected)
} catch (IllegalArgumentException ignored) {
return false // malformed entry: ignore it and try the next one
}
}
}
if (!ok) throw new SecurityException('Invalid Masnet webhook signature') // the iFlow answers with an error status
message.setProperty('signature_verified', 'true')
return message
}Respuesta
Si el script termina sin excepción, el iFlow responde con éxito (2xx). Si lanza SecurityException, el iFlow responde con un estado de error y Masnet reintenta; esto es lo esperado ante una firma inválida. Mantenga el procesamiento posterior desacoplado (por ejemplo, con el mecanismo de mensajería de su iFlow) para responder bien antes de 15 segundos.
Prueba con el evento de prueba
- Despliegue el iFlow.
- En Masnet, seleccione Probar destino y Enviar prueba.
- Compruebe en el monitor de mensajes de SAP que el mensaje llegó con
Masnet-Event-Type: com.masnet.integration.test.v1y quesignature_verifiedestrue. - En Masnet, el resultado muestra el código de respuesta y los tiempos medidos.
Operación: duplicados y orden
- Idempotencia.
webhook-ididentifica el evento y se conserva en los reintentos y los reenvíos. Registre los identificadores ya procesados y descarte los repetidos con una respuesta 2xx. - Orden. Cada recurso (
subject) lleva unsequencecreciente. Descarte un evento cuyosequencesea menor o igual al último que aplicó para esesubject. Los eventos de recursos distintos no tienen orden entre sí. - Reintentos. Masnet reintenta cualquier respuesta distinta de 2xx durante un máximo de 72 horas; el calendario completo está en Webhooks.
- Rotación del secreto. Al rotar con superposición, el secreto anterior sigue vigente 24 horas y
webhook-signaturetrae dos firmasv1,…separadas por un espacio. Acepte el evento si cualquiera coincide.
Diagnóstico
Masnet registra el código de respuesta de cada intento en el historial de entregas. Estos son los efectos de cada respuesta del receptor:
| Respuesta del receptor | Efecto en Masnet |
|---|---|
| 2xx | Entrega exitosa. Significa recepción, no procesamiento de negocio. |
| 3xx | No se sigue la redirección. Cuenta como fallo y se reintenta; el historial muestra el Location sin su cadena de consulta. |
| 400, 401, 403, 404 y otros 4xx o 5xx | Fallo reintentable hasta 72 horas desde el inicio del ciclo de entrega. |
| 410 | Masnet desactiva el destino y cancela lo pendiente. Use 410 solo si el destino debe dejar de recibir eventos. |
| 413 | El evento se marca como no entregable a ese destino y no se reintenta. |
429 o 503 con Retry-After | Se respeta Retry-After (segundos o fecha HTTP) hasta un máximo de 1 hora. |
| Sin respuesta en 15 segundos | Tiempo agotado (5 s de conexión, 15 s en total). Es un fallo reintentable: responda 2xx y procese de forma asíncrona. |
| Error del script por firma inválida | Verifique el secreto, que el script reciba los bytes originales y la hora del servidor (tolerancia de 5 minutos). |