Confirmez la destination avant l’expédition
Delivery Intelligence permet à un commerçant de proposer une destination, de demander au destinataire de la confirmer ou de la corriger, puis de recevoir un résultat fiable avant la préparation de la livraison.
Parcours de bout en bout
- Votre backend crée une demande de confirmation.
- En Production, Alkax envoie au destinataire un lien sécurisé à durée limitée. Sandbox enregistre une notification supprimée dans son récepteur de test ; ouvrez le lien retourné manuellement.
- Le destinataire examine la destination proposée sans avoir besoin d’un compte Alkax.
- Il la conserve, sélectionne un autre lieu Alkax actif ou crée un lieu manquant.
- Il peut ajouter une photo privée de l’entrée, qui reste indisponible jusqu’à son approbation par la modération.
- Alkax enregistre l’instantané confirmé ou corrigé.
- Votre backend récupère le résultat ou reçoit un webhook signé.
Avant de commencer
Créez un projet dans la Console développeur et émettez un identifiant propre à l’environnement avec les portées requises :
delivery:writepour créer, annuler, renvoyer et rejouer ;delivery:readpour récupérer les résultats.
Les identifiants Sandbox ont la forme ak_test_<key-id>.<secret> et ceux de Production utilisent ak_live_<key-id>.<secret>. Conservez la valeur complète dans un gestionnaire de secrets. Elle n’est affichée qu’une fois et ne doit jamais être placée dans du code navigateur ou mobile.
export ALKAX_BASE_URL="https://api.alkax.io"
export ALKAX_API_KEY="ak_test_<key-id>.<secret>"
1. Préparer votre webhook
Utilisez un point de terminaison HTTPS public qui renvoie 2xx uniquement après l’acceptation durable de l’événement :
POST https://merchant.example/webhooks/alkax
Alkax rejette les destinations non HTTPS, de boucle locale et de réseau privé, et ne suit pas les redirections.
2. Créer une confirmation
Remplacez expiresAt par un horodatage UTC futur et recipientContact par une boîte de test que vous contrôlez avant d’envoyer cette requête. Le modèle de corps ci-dessous n’est pas exécutable tant que ces valeurs ne sont pas renseignées.
Générez une clé d’idempotence par tentative logique d’adressage d’une commande. Réutilisez-la uniquement pour réessayer une requête identique.
L’exemple Sandbox n’envoie pas de courriel. Production peut envoyer du courrier : obtenez d’abord le consentement du destinataire.
curl --request POST "$ALKAX_BASE_URL/api/v1/address-confirmations" \
--header "X-Alkax-Key: $ALKAX_API_KEY" \
--header "Idempotency-Key: order-1042-address-v1" \
--header "Content-Type: application/json" \
--header "Accept-Language: fr" \
--data '{
"merchantReference": "ORDER-1042",
"recipientContact": "[email protected]",
"locale": "fr",
"expiresAt": "<UTC timestamp in the future>",
"proposedAddress": {
"placeId": null,
"title": "Bonaberi depot",
"plusCode": "6GCR+8J",
"settlement": "Douala",
"locality": "Bonaberi",
"country": "CM",
"buildingName": null,
"floor": null,
"unit": null,
"latitude": 4.08,
"longitude": 9.66
},
"callbackUrl": "https://merchant.example/webhooks/alkax"
}'
La réponse initiale 201 contient :
id, l’identifiant de la demande Alkax ;recipientUrl, renvoyé uniquement à la création initiale ;webhookSigningSecret, renvoyé uniquement lors de la création initiale lorsque Alkax génère le secret de rappel.
Conservez immédiatement le secret de signature. Alkax ne peut pas le renvoyer.
3. Lire le cycle de vie
curl "$ALKAX_BASE_URL/api/v1/address-confirmations/<request-id>" \
--header "X-Alkax-Key: $ALKAX_API_KEY" \
--header "Accept-Language: en"
| État | Signification |
|---|---|
Pending | Créée ; le destinataire n’a pas ouvert le lien actif |
Opened | Le destinataire a ouvert le parcours de confirmation |
Confirmed | Le destinataire a accepté la destination |
Corrected | Le destinataire a sélectionné ou créé une autre destination |
Expired | La demande à durée limitée a expiré sans finalisation |
Cancelled | Le commerçant a mis fin à la demande |
Utilisez l’instantané confirmé immuable pour la livraison. Ne le reconstruisez pas à partir du texte d’affichage ou de la proposition initiale. Lorsqu’une évaluation a été effectuée, la réponse peut aussi contenir confidenceScore, confidenceBand et confidencePolicyVersion.
4. Vérifier les signatures des webhooks
Chaque webhook contient :
X-Alkax-Event-Id: <UUID>
X-Alkax-Timestamp: <Unix seconds>
X-Alkax-Signature: v1=<hex HMAC-SHA256>
Les octets signés sont :
<timestamp>.<raw HTTP request body>
Exemple Node.js :
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyAlkaxWebhook({ rawBody, timestamp, signature, secret }, nowSeconds = Math.floor(Date.now() / 1000)) {
if (!Buffer.isBuffer(rawBody) || typeof timestamp !== "string" || !/^\d{1,12}$/.test(timestamp) ||
Math.abs(nowSeconds - Number(timestamp)) > 300 || typeof signature !== "string" ||
!/^v1=[a-f0-9]{64}$/.test(signature) || typeof secret !== "string" || !secret) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.`, "utf8")
.update(rawBody)
.digest();
const supplied = Buffer.from(signature.slice(3), "hex");
return supplied.length === expected.length && timingSafeEqual(supplied, expected);
}
Traitez l’événement dans cet ordre :
- Rejetez les horodatages hors d’une fenêtre de fraîcheur de cinq minutes.
- Vérifiez la signature sur le corps brut non modifié, puis comparez
eventIdanalysé à l’en-tête. - Conservez l’identité environnement/événement et l’empreinte du corps brut avec une contrainte unique.
- Si l’identité existe avec la même empreinte, renvoyez
2xxsans répéter les effets. Une empreinte différente est un échec d’intégrité ; n’appliquez aucun changement d’état. - Validez l’enregistrement de l’événement et votre modification m étier avant de renvoyer
2xx.
Alkax réessaie après les délais d’expiration et les réponses 408, 425, 429 et 5xx. L’identifiant d’événement et le corps restent stables entre les tentatives et les rejeux manuels.
Actions opérationnelles
| Action | Point de terminaison | Portée requise |
|---|---|---|
| Annuler | POST /api/v1/address-confirmations/{id}/cancel | write |
| Renvoyer le lien destinataire | POST /api/v1/address-confirmations/{id}/resend | write |
| Rejouer un webhook définitivement échoué | POST /api/v1/address-confirmations/webhooks/{eventId}/replay | write |
Le renvoi renouvelle le jeton destinataire et est limité en fréquence. Une demande annulée, finalisée ou expirée ne peut pas envoyer de notification permettant encore une action.
Erreurs et nouvelles tentatives
400: corrigez la requête ; ne la renvoyez pas sans changement.401: l’identifiant est absent, mal formé, invalide, expiré ou révoqué.403: l’identifiant ne possède pas la portée requise ou l’accès à la production.404: la demande n’existe pas dans le tenant de ce commerçant.409: examinez le conflit de cycle de vie ou d’idempotence.429: respectezRetry-Afteret espacez les tentatives avec une variation aléatoire.5xx: réessayez avec un délai exponentiel et la même clé d’idempotence.
Les messages d’erreur destinés aux utilisateurs existent en anglais, français, arabe, portugais et swahili. Conservez les codes lisibles par machine au lieu de comparer le texte traduit.
Liste de contrôle de mise en production
- Conservez les identifiants de projet et secrets de signature dans un gestionnaire de secrets.
- Masquez les contacts et URL destinataire, instructions privées et médias d’entrée dans les journaux.
- Préservez le corps brut du webhook.
- Dédupliquez par identifiant d’événement.
- Déclenchez des alertes en cas de réponses
429/5xxdurables et d’accumulation de webhooks en attente. - Testez la confirmation, la correction, l’expiration, l’annulation, le renvoi et la livraison de webhooks en double.
Contrat de rappel récupérable
Pour pouvoir récupérer une création, générez et chiffrez durablement un secret par tentative avant d’appeler Alkax. Définissez le champ facultatif callbackSigningSecret avec exactement 64 caractères hexadécimaux minuscules issus de 32 octets aléatoires ; il exige callbackUrl. Le secret est utilisé comme texte UTF-8 pour HMAC et n’est jamais renvoyé. S’il est omis, Alkax génère un secret et le renvoie une seule fois dans webhookSigningSecret.
Définissez callbackSchemaVersion sur v2 pour recevoir le rappel minimal explicite ci-dessous. Omettez-le ou choisissez v1 pour conserver le rappel historique avec des propriétés imbriquées PascalCase et un état numérique. Les réponses HTTP de confirmation utilisent camelCase et des états textuels ; ces trois formats sont distincts. La version effective est conservée, et la reprise préserve l’identifiant et les octets exacts de l’événement en attente.
Les créations répétées à l’identique conservent la même demande et renvoient des champs à usage unique nuls, même après l’échéance du lien initial. Une nouvelle opération exige toujours une expiration future. Conservez le contenu original exact et la clé d’idempotence ; toute option modifiée crée un conflit. Chaque nouvelle tentative exige sa propre référence opaque dans le projet et l’environnement. La réponse privée canonique inclut deliveryInstructions, createdAt et cancelledAt ; ce n’est pas une projection publique de tâche.
Dans le rappel ci-dessous, comparez eventId à l’en-tête, vérifiez la signature brute et sa fraîcheur, éliminez durablement les doublons, puis récupérez le résultat canonique authentifié. Le rappel minimal ne contient ni coordonnées, ni instructions, ni capacités. Les événements d’abonnement du projet restent un canal distinct. Leurs identités de création et d’annulation sont stables par confirmation et transition ; cela ne supprime pas les doublons sémantiques du canal historique par confirmation.
{
"schemaVersion": "v2",
"eventId": "11111111-1111-4111-8111-111111111111",
"type": "address_confirmation.confirmed",
"occurredAt": "2026-10-25T12:00:00+00:00",
"request": {
"id": "22222222-2222-4222-8222-222222222222",
"merchantReference": "opaque-attempt-reference",
"status": "Confirmed",
"expiresAt": "2026-10-26T12:00:00+00:00"
}
}
Sandbox conserve les notifications des destinataires dans un récepteur de test sans envoyer de courriel. Testez manuellement le lien retourné ; l’activation de Production exige une validation hébergée.
Déployez la version compatible du fournisseur avant d’utiliser les options supplémentaires. L’état canonique enregistré peut rester Pending ou Opened après l’échéance jusqu’à la détection d’expiration ; la réconciliation en arrière-plan reste une condition de publication. L’échéance du lien n’invalide pas une capture déjà complétée. Les changements de repère privé peuvent conserver Confirmed lorsque les identifiants de lieu ne changent pas : comparez la capture canonique exacte et gérez une version de destination au lieu de vous fier uniquement au classement. Le contrat de récupération seul ne met pas en œuvre le consentement ni la divulgation de VolXpr. La présentation privée hébergée est activée séparément par l’option de contexte ci-dessous.
Visites privées de la communauté
Pour une visite à domicile privée, ajoutez ces champs à la requête de création normale :
{ "presentationContext": "private_visit", "locale": "fr" }
presentationContext est facultatif ; son absence conserve l’expérience de livraison habituelle. Seule la valeur private_visit est acceptée. Elle est enregistrée avec la confirmation et fait partie de l’idempotence : la modifier exige une nouvelle tentative et référence. Les réponses authentifiées de création/GET renvoient cette valeur liée au serveur pour détecter un ancien fournisseur qui ignore l’option.
La page précise que le domicile reste privé, masque la création de lieu public et les photos, et propose un repère exact personnalisé ou, au choix, un point de rencontre public existant. La validation ne crée ni ne modifie aucun lieu public. Le serveur rejette entranceImageReference pour les visites privées, même hors interface.
La réponse publique fournit effectiveLocale, normalisé en en, fr, ar, pt ou sw (anglais par défaut). L’e-mail et la page initiale utilisent la même langue demandée. Le changement de langue conserve la saisie et l’arabe utilise RTL. Les pannes temporaires offrent une nouvelle tentative sans déclarer le lien invalide.
Jusqu’à l’expiration du lien initial, répéter la validation avec un jeton consommé renvoie seulement un reçu : proposedAddress, confirmedAddress et les médias sont nuls. GET reste indisponible après validation ; le reçu ne rouvre pas le lieu et ne change pas le résultat. Les autres limites de cycle de vie et de transitions terminales concurrentes restent celles indiquées ci-dessus.