Global Authorization Webhook Spec

Billpocket offers a webhook POST notification whenever an authorization is approved for a certain user in each and any of their devices.

Register your Webhook

Development: Development Dashboard

Production: Production Dashboard

Only approved transactions are posted through this service

Conditions for webhook-based authorization delivery are:

  • The 3rd-Party Endpoint must meet the following requirements (both for QA and Production environments):

    • Must receive messages using HTTP POST

    • Must have a valid SSL certificate from a valid Certificate Authority (no self-signed certs)

    • Must take less than 2 seconds to respond with a HTTP 200/OK Code

    • Must accept JSON payloads (Content-Type: application/json)

    • Must support TLS 1.2 or newer

  • JSON structure is as follows

{ "result": (String) Result of the transaction (as of now, 'aprobada' at all times), "amount": (String) Amount due, "tip": (String, Optional) Tip Amount (not included in "amount"), "payments": (Integer) Deferred payments plan for the transaction if applicable and selected, "authorizationTime" : (String) Authorization time, RFC3339 "reference": (String) Transaction description, "transactionid": (String) Transaction ID in Billpocket's system, "authorization": (String) Transaction authorization string, "creditcard": (String) Last 4 digits of the card's Primary Account Number, "cardtype": (String) Issuer [VISA | MASTERCARD | CARNET | AMERICAN EXPRESS], "arqc": (String, EMV only) ARQC String for this transaction, "userID" : (Integer) User ID of the transaction owner "aid": (String, EMV only) Chip's Application ID, "applabel": (String, EMV only) Chip's Application Label, "url": (String) Unique identifier to access this transaction ticket, "email": (String, Optional) Email to which this transaction's ticket is sent, "phone": (String, Optional) Phone number to which this transaction's ticket is sent via SMS, "cardBrand": (String, Optional) Card Issuer Network, "cardIssuer": (String, Optional) Card Issuer Bank, "cardCountry": (String, Optional) Card Country Code (ISO 3166-1 alpha-2), "cardClass": (String, Enum, Optional) DEBIT or CREDIT Card, "identifier": (String, Optional) Identifier of the transaction generated by the merchant }

Digital Signature

Digital Signature is provided with every POST notification.

Signature values are accesible vía the request headers:

X-BP-Signature
Contains the Base64 encoded value of the signature for the delivered payload


X-BP-SignatureKey
Contains the Key Index of the private key used to sign this message.

To get a hold of the Public Key for signature verification, you must append the key index to the following URL:

https://keys.billpocket.com/webhook/

And use .pem or .der extension depending on the preferred format for your application to read this public key. For Key Index “testKey1”, the public key is located at:

We encourage you to cache or otherwise store the public key contents on your side to speed up the signature verification process on successive notifications. Keys won’t change over time, but the key index may be updated to use a new pair of keys.

Code Examples

We have tailored snippets in some of the most common languages out there, if you’re using other language with no examples, the process outline should be enough for you to reach a successful implementation

Java

Assuming you're using Spring Boot, get hold of the Signature and Signature Key values and perform verification on the received payload:

import org.apache.commons.io.IOUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { static { Security.addProvider(new BouncyCastleProvider()); } @PostMapping(value = "/billpocketWebhook", consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity<String> billpocketWebhook(@RequestHeader("X-BP-Signature") String signature, @RequestHeader("X-BP-SignatureKey") String keyIndex, @RequestBody String payload){ byte[] signatureBytes = Base64.getDecoder().decode(signature); String keyURL = "https://keys.billpocket.com/webhook/" + keyIndex + ".der"; byte[] publicKeyFileBytes= IOUtils.toByteArray(new URL(keyURL)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyFileBytes)); Signature publicSignature = Signature.getInstance("SHA256withRSA", BouncyCastleProvider.PROVIDER_NAME); publicSignature.initVerify(publicKey); publicSignature.update(payload.getBytes(StandardCharsets.UTF_8)); boolean isVerifiedRequest = publicSignature.verify(signatureBytes); if (!isVerifiedRequest){ // Error, signature validation went wrong return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } // Verified signature, proceed } }

Previous code has the following dependencies

<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.54</version> </dependency>

 


PHP

The following snippet assumes you use Lumen/Laravel/Symfony as your framework, routing configuration has been left off. The core signature validation functionality is standard PHP, so it should be trivial to adjust it to any other framework.

 


Node.js

The following snippet assumes you use Node.js, Express and Axios, routing configuration has been left off. The core signature validation functionality is standard Javascript. Be sure to use a body parser config that lets you get hold of the raw JSON, otherwise you might get a different value and the verification step will fail.

 

© Billpocket, 2018