OVO Partner Integration Documentation

Technical Documentation SNAP - Linking and Payments

SNAP Open API v1.0.7

Overview of OpenAPI for Linking and Payments

Host :

  • Staging : https://app.byte-stack.net
  • Production : https://apigw.ovo.id
Method Endpoint Usage
POST /OVOSNAP/v2.0/oauth/account/registration-account-binding Account Binding
POST /OVOSNAP/v2.0/access-token/b2b2c Access Token Request
POST /OVOSNAP/v2.0/access-token/b2b2c Access Token Refresh
POST /OVOSNAP/v1.0/access-token/b2b Generate System Token (B2B)
GET /user/v2/account/lookup Lookup Phone No
POST /OVOSNAP/v2.0/oauth/account/registration-account-unbinding Account Unbinding
POST /OVOSNAP/v2.0/balance-inquiry Balance Inquiry
POST /OVOSNAP/v2.0/debit/payment-host-to-host Direct Debit
POST /OVOSNAP/v2.0/debit/refund Direct Debit Refund
POST /OVOSNAP/v2.0/debit/status Direct Debit Inquiry Status

Signature   API Header

API Headers

All the APIs mentioned are to follow the standard header format mentioned here, unless stated otherwise

Parameter Description Attribute Example
Content-Type String represents indicating the media type of the resource Mandatory application/json, application/pdf
Authorization Represents access_token of a request; string starts with keyword “Bearer ” followed by accessToken (e.g. Bearer eyJraWQiOi...JzcIiwiY) No Need For
  1. Request B2B Access Token API
  2. Refresh B2B2C Access Token API
  3. Account Binding API
Mandatory For
  1. Request New B2B2C Access Token API
    1. linkageToken → From Account Binding API Response
  2. Account Unbinding API
    1. 1st API Call: B2B2C Access Token
    2. 2nd API Call: use the token received in the first call
  3. Direct Debit API
    1. B2B2C Access Token
  4. Refund API
    1. B2B2C Access Token
    2. B2B Access Token
  5. Direct Debit Status API
    1. B2B2C Access Token
Bearer gp9HjjEj813Y9JGoqwOeOPWbnt4CUpvIJbU1mMU4a11MNDZ7Sg5u9a
X-CLIENT-KEY Client's client_id. Use this for
  1. B2B2C Access Token
  2. B2B Access Token
Conditional oamerchantg
X-PARTNER-ID Client's client_id. Use this for
  1. Account Binding
  2. Account Unbinding
  3. Direct Debit
  4. Refund
  5. Direct Debit Status
Conditional oamerchantg
X-TIMESTAMP Client's current local time in yyyyMMddTHH:mm:ss.SSSTZD format Mandatory
X-SIGNATURE
  1. Use Asymmetric for Token request & Account Binding
  2. Use Symmetric for Payment, Balance Inquiry, Check Status, Account Unbinding
  3. Use Specific Signature for Generate SingleUseToken and Lookup API
Mandatory Please check Signature Section
X-DEVICE-ID Device identification on which the API services are currently being accessed by the end-user (customer) - String (400) Mandatory
X-EXTERNAL-ID Unique ID to avoid duplication. ID reset for every 24 hours. String (36) Mandatory

Signature Asymetric

Signature asymmetric is used by OVO to verify that your access token request is not altered by attackers. Generate Signature Asymmetric for Header of Access Token B2B / B2B2C SHA256withRSA is used to generate the signature with your Private Key as the key.

/*
How to use:
- Configure the client ID via request headers with key as x-client-key.
- Configure the private key via environment variable with key as {clientID}_private_key.
- Script will generate / replace environment variables listed in the Output section.

Output (Environment variables):
- gen_signature -> add to X-Signature headers
- gen_timestamp -> add to X-Timestamp headers
*/

eval( pm.environment.get('pmlib_code') )

var moment = require('moment')

const clientID = pm.environment.get("clientID")
var privateKey = pm.environment.get("_private_key")
const timestamp = moment().format("YYYY-MM-DDThh:mm:ss.SSSZ")

console.log("clientID: " + clientID)
console.log("privateKey: " + privateKey)
console.log("timestamp: " + timestamp)

const msg = `${clientID}|${timestamp}` // Please use this for Token Request

const msg = `${method}:${fullPath}:${finalBody}:${timestamp}` // Please use this for Account Binding

console.log("msg: " + msg)

var CryptoJS = require("crypto-js")
const hashedMsg = CryptoJS.SHA256(msg)
console.log("hashedMsg: " + hashedMsg)

var sig = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"})
privateKey = pmlib.rs.KEYUTIL.getKey(privateKey)
sig.init(privateKey)
const hash = sig.signString(msg)

console.log("hash: " + hash)

pm.environment.set("gen_timestamp", timestamp)
pm.environment.set("gen_signature", hash)

Signature Symmetric

Signature is used to verify that your open API service request is not altered by attackers.

Generate Signature Symmetric

SHA-512 HMAC is used to generate the signature with your Client Secret as the key.

/*
How to use:
- Configure the client ID via request headers with key as x-partner-id.
- Configure the access token via request headers with key as Authorization and format of value as Bearer {access_token}.
- Configure the secret key via environment variable with key as {clientID}_secret_key.
- Script will generate / replace environment variables listed in the Output section.

Output (Environment variables):
- gen_signature -> add to X-Signature headers
- gen_timestamp -> add to X-Timestamp headers
*/

eval( pm.globals.get('pmlib_code') )

var moment = require('moment')
var CryptoJS = require("crypto-js")

const clientID = pm.environment.get('clientID');
const accessToken = pm.environment.get("access_token");
const timestamp = moment().format("YYYY-MM-DDThh:mm:ss.SSSZ")
const body = pm.request.body.raw
const fullPath = pm.request.url.getPathWithQuery()
const method = pm.request.method

var secret_key = pm.environment.get("_secret_key")

console.log("clientID: " + clientID)
console.log("accessToken: " + accessToken)
console.log("timestamp: " + timestamp)
console.log("body: " + body)
console.log("fullPath: " + fullPath)
console.log("method: " + method)
console.log("secret_key: " + secret_key)

const hashedBody = CryptoJS.SHA256(body).toString()
const finalBody = hashedBody.toLowerCase()

console.log("hashedBody: " + hashedBody)
console.log("finalBody: " + finalBody)

const msg = `${method}:${fullPath}:${accessToken}:${finalBody}:${timestamp}`

console.log("msg: " + msg)

const hash = CryptoJS.HmacSHA512(msg, secret_key).toString()

console.log("hash: " + hash)

pm.environment.set("gen_timestamp", timestamp)
pm.environment.set("gen_signature", hash)
pm.environment.set("api", "/OVOSNAP/v2.0/debit/payment-host-to-host'")

Specific Signature

Signature is used to verify that your open API service request is not altered by attackers.

JAVA
Signature Calculation Util

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.digest.HmacUtils;


public class Calculator {

  public static String get(String appId, String key, String time, String method,String url ,String body) {

    final String urlParams = method+" "+url;

    final String secretKey = DigestUtils.sha256Hex(key);

    final String baseMessage = createBaseMessage(appId, time, urlParams, body.getBytes());

    String signature = HmacUtils.hmacSha256Hex(secretKey, baseMessage);

    return signature;
  }


  public static String createBaseMessage(String appId, String time, String urlParam, byte[] byteBody) {

    final String payloadBase64 = Base64.encodeBase64URLSafeString(byteBody);
    return appId + time + urlParam + payloadBase64;
  }

}

Usage

Calculator.get("ovo_partner", "7b3e13a21764563721fbeef29c3b3102", "1523111100000", "POST", "/user/v1/oauth/otp/generate", "body");

Expected : "ddc622679740a983258d8783d5e721849d89919940fe63ca185a8f06471f00a6"

Notes:

  1. Endpoint URL complete including all parameters on URL related
  2. For parameters minify(Request Body), in case there is no Request Body then use the empty string

Error Code

Here is the list of error codes that can be returned.

HTTP Code Error Code Error Message (Indonesian)
401 4017300 Unauthorized. [HMAC mismatch]
400 4007301 Invalid timestamp format [X-TIMESTAMP]
504 5047300 Timeout
400 4007300 Invalid field format [clientId/clientSecret/grant_type]

How to Generate PKCS1 Private Key and Public Key

Generate Private Key:
For Openssl version 1.x.x
openssl genrsa -out private.pem 4096

For Openssl version 3.x.x and above
openssl genrsa -traditional -out private.pem 4096
Generate PKCS1 Public Key:
openssl rsa -in private.pem -RSAPublicKey_out -out public.pem

SNAP Auth Validator Utility

This snippet code is an example on how to check SNAP Auth validity

package main

import (
  "crypto"
  "crypto/hmac"
  "crypto/rsa"
  "crypto/sha256"
  "crypto/sha512"
  "crypto/x509"
  "encoding/hex"
  "encoding/pem"
  "errors"
  "fmt"
  "strings"
)

const PrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAghghSSsbhCKBllAWbLmqdvqkg/b/yrRXE3PHzH2zkOVUgV/u
YtmLAaMUOt4UGiBKQa4TcRPPf6ITQpHCrpgpCadQAvikTFF+BW7pBdSvfZQVsscf
9MGLFkz7+uQlcnrDotHjeQ1Ei4wWU/kqe5rLRJZMl2+91yxWeaLTgockF6Mb8XrR
DI4DdaxBY0Lr0B20QqOFgM54ti1i628vAQGFFZPw2rgKufLOmRkJ9A8+vjVhXxC3
8sB2Is81okJcQKsLQZT4IqLxayF8jfr2j9mZtTCpoBdjQIVcbCbOFKtNN7c1j7wy
c8T9lWPPY43vKzfNf73w9JzKLfH6M8LDfJFIzwIDAQABAoIBAAsi2aaTxBU5hvJB
BMpl0kvBBNgvCpJlgZausIm2sOpUVzmD5robeSS4uwCXBg1+ehzJM+zYD0kTrKZk
J9AeQlULN3QpqJbH2wvIBLZ1EFilln3pQbkH4EoWaPN/GB2GmLyVTu2LzzRK15Z0
m8hc5c2HOCM2c3+50eUzpPtlaheDxRP5STPZVVpz87UyK09FAstzGNYmzZ733lDV
JoVGaa7hWTxzm4M4oxo2Bb06FSW2g3HKkqWC8PGBQ4nxDo7Xy3AkEYI3fGsYXbjC
fn/xMly/ARFWM1sICJTHzNdpHPIBuYtRJ8bEgc1Pew8g+JfAA1BFCWvYqcwjxIr9
6+JEfsECgYEA3AQdKGGq2sSPghbBbeWMotXxzXtwxJjxFAvRR32riIiu/qdtoC29
rsBpjvSs4jFVd4II6cE7xy0xJLtOU6asNIfhJbL3k3/f+HCN2PzkHwsNHIG6Xb8v
m9W+YCJudOmQJzfWUDcwfYk+uL0f6DnhGpazr5HkPmvicVzEsV3cRBECgYEAl18Q
8SwA8qwf18wCbVTgX/u2XD33lnabnz8a4F/5N4pKs7FKPDz7bgW4Jyik9+THZsrG
zepw8wXxzNqyTLC0WmyyYLTNp1x7EUBfAM40QYjydXcbXrTti28OGAKTh/DB3OvO
vl9uO+FfwWFXIdVK4Ie8JWsFrn5ciBM04aY4Ht8CgYBgeiVXCczj9YGAZ/4V9KzA
0tQfaNvAOditE6mHkeHgEx+5Zy25KZWdxZ4EI+KTpVJ2/zxtVGCkLHr6QnBMWi/1
MQhXgazyrwZFaQWqeuqFelEbiP9yEF4OFaJPgYmyFqExsVh3AFxxD/fDBpuxN4Aw
KplMicruXFyFnUpbBG+MIQKBgGFZfmfcSO/IyuHaDmWKBJM2Kt2/7I8T0Jnl178d
egXCJrDSAFAlV/42J2znstDKjYMKPjkH4YQp+owoyiqQKi1NYprXLLvJukwp/e9i
rjDHhkcNRsjtyye1UHcYkREIQWV3Mgs1DIvuMcsIcyULK5CjOtlFru29znyk/Ylx
gP45AoGBAIBkUjJo+xd7sNNhNCW07rUiAWegEHOIk5pWzwLDlY2AVK9/tlNBI6wD
GWLT6L5B9reN+982tOEBAYlP/p2e+eUBy8BPgvqbsPD3yHD3pR8WibrNEVwGhL8P
SC/C9azKvNlBsN1HRWdRrEN9Fx5Eu15XNBlKX1RIiHkv8iW1n6F9
-----END RSA PRIVATE KEY----
`

const PublicKey = `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAghghSSsbhCKBllAWbLmqdvqkg/b/yrRXE3PHzH2zkOVUgV/uYtmL
AaMUOt4UGiBKQa4TcRPPf6ITQpHCrpgpCadQAvikTFF+BW7pBdSvfZQVsscf9MGL
Fkz7+uQlcnrDotHjeQ1Ei4wWU/kqe5rLRJZMl2+91yxWeaLTgockF6Mb8XrRDI4D
daxBY0Lr0B20QqOFgM54ti1i628vAQGFFZPw2rgKufLOmRkJ9A8+vjVhXxC38sB2
Is81okJcQKsLQZT4IqLxayF8jfr2j9mZtTCpoBdjQIVcbCbOFKtNN7c1j7wyc8T9
lWPPY43vKzfNf73w9JzKLfH6M8LDfJFIzwIDAQAB
-----END RSA PUBLIC KEY-----
`

const RawBody = `{"foo": "bar"}`

func main() {
    specs := Specs{
        ClientID: "test",
        PrivateKey: PrivateKey,
        PublicKey: PublicKey,
        ClientSecret: "foo-bar",
        HTTPMethod: "POST",
        URI: "/foo/bar",
        RawBody: []byte(RawBody),
        Timestamp: "2022-03-10T04:02:11.108+07:00",
        AccessToken: "foobar",
    }

     AccessTokenSignature := "730f96f44e95c3b9fe7cb3ed94efa0cf8424813c0aa5974bd1c66ca0dcd00150107decca4de63b5b776cc7f7db25cf67ba70740438af76776bd15cba75e4604ae97e8b27f8ce36b87ad34ae6eaf4c3390a80db7e353b8384c85d8bf95ff8095e183d45520d739e12228fdbdf673b361ab97ac6de1bd544fd9b850d3cb5f91e47d6d7975565004d7551e23ca5bf42db9090e200f65789eaa74ff50ecb3a316786831d0f89e66230fcbd7e7195ba9b5f40f46b3b7001ab6b8f47d7be68656f5be0a8ff4a51bebb2c2741e997ca33adcec1ec58137af5a61b6234f7791399be6c1fcdfd1c2650a39d29ff8c38c6350754255b4970db1f44da43175798637b47784c"
     SymmetricSignature := "9b4ad98c7e4107ac7008576fb0347eb2369d540f929435a438d81094d4cb6b65348e06cb9af4660deb86a4508694f8431b30351e57f9874662e3740779fcdc4b"
     AsymmetricSignature := "533c8f937b285ac9d258f85b01d48ad8a1cf66d7418686dcd030bb6d6775bd88b0526d4abe92e8f3a2142efd8905e4fa77460a5affc064b57a3607dc6849d3fb3b7895d425e027f01f43f067c8bfcd1c25a039d2d9d3a8f1df36ab9d2607778b6d1f846e2f3e606aa87c8eca9b1a6a4bd35b072ea7beb0ec80714594ade85eede4992892eaaa1f3a46f48cd17f2b6e46753e76546fa39990e0f4e7a5f077f2586092af0bfbff38ceceebf7dfcafe2e1a8c3b3cca003d4802aebb1f659b40bab08f43aaa9349df9d69e6c3e88cae7b5a1721bf1c06f9c081e10fe6f8ef087fd4e3e616a4ae35040612a4abdbda211f02a386d07a988906a06e2bd29c1722583f3"
    
     err := Validate(AccessTokenSignature, AccessToken, specs)
     if err != nil {
         println(err.Error())
     } else {
         println("Validation success.")
     }
    
     err = Validate(SymmetricSignature, Symmetric, specs)
     if err != nil {
         println(err.Error())
     } else {
         println("Validation success.")
     }
    
     err = Validate(AsymmetricSignature, Asymmetric, specs)
     if err != nil {
         println(err.Error())
     } else {
         println("Validation success.")
     }
}

type ValidationType string

const (
    AccessToken ValidationType = "access-token"
    Symmetric = "symmetric"
    Asymmetric = "asymmetric"
)

var (
    ErrInvalidHMAC = errors.New("invalid HMAC")
    ErrInvalidValType = errors.New("invalid validation type")
)

type Specs struct {
    ClientID string
    PrivateKey string
    PublicKey string
    ClientSecret string
    HTTPMethod string
    URI string
    RawBody []byte
    Timestamp string
    AccessToken string
}

func Validate(signature string, valType ValidationType, specs Specs) error {
     switch valType {
     case AccessToken:
         return validateAccessTokenSignature(signature, specs)
    
     case Symmetric:
         return validateSymmetricSignature(signature, specs)
    
     case Asymmetric:
         return validateAsymmetricSignature(signature, specs)
    
     default:
         return ErrInvalidValType
     }
}

func validateAccessTokenSignature(signature string, specs Specs) error {
     data := fmt.Sprintf("%s|%s", specs.ClientID, specs.Timestamp)
    
     decodedSignature, err := hex.DecodeString(signature)
     if err != nil {
         return err
     }
    
     ok, err := SHA256WithRSAValidate(decodedSignature, []byte(specs.PublicKey), []byte(data))
     if err != nil {
         return err
     }
    
     if !ok {
         return ErrInvalidHMAC
     }
    
     return nil
}

func validateSymmetricSignature(signature string, specs Specs) error {

     transformedBody, err := transformBody(specs.RawBody)
     if err != nil {
         return err
     }
    
     data := fmt.Sprintf("%s:%s:%s:%s:%s", specs.HTTPMethod, specs.URI,
         specs.AccessToken, transformedBody, specs.Timestamp)
    
     ok := SHA512HMACValidate([]byte(data), []byte(signature), []byte(specs.ClientSecret))
     if !ok {
         return ErrInvalidHMAC
     }
    
     return nil
}

func validateAsymmetricSignature(signature string, specs Specs) error {
     transformedBody, err := transformBody(specs.RawBody)
     if err != nil {
         return err
     }
    
     data := fmt.Sprintf("%s:%s:%s:%s", specs.HTTPMethod, specs.URI, transformedBody, specs.Timestamp)
    
     decodedSignature, err := hex.DecodeString(signature)
     if err != nil {
         return err
     }
    
     ok, err := SHA256WithRSAValidate(decodedSignature, []byte(specs.PublicKey), []byte(data))
     if err != nil {
         return err
     }
    
     if !ok {
         return ErrInvalidHMAC
     }
    
     return nil
}

func transformBody(body []byte) (string, error) {
     sha256Body := sha256.Sum256(body)
     hexBody := hex.EncodeToString(sha256Body[:])
     finalBody := strings.ToLower(hexBody)
    
     return finalBody, nil
}

func SHA256WithRSAValidate(sig, pubPEM, data []byte) (bool, error) {
     hashed := sha256.Sum256(data)
    
     block, _ := pem.Decode(pubPEM)
     if block == nil {
         return false, errors.New("failed to decode public PEM")
     }
    
     pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
     if err != nil {
         return false, err
     }
    
    
     err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], sig)
     if err != nil {
         if errors.As(err, &rsa.ErrVerification) {
             return false, nil
         }
    
             return false, err
         }
    
    return true, nil
}

func SHA512HMACValidate(message, messageMAC, key []byte) bool {
     mac := hmac.New(sha512.New, key)
     mac.Write(message)
     expectedMAC := mac.Sum(nil)
     expectedMacEncoded := hex.EncodeToString(expectedMAC)
    
     return hmac.Equal(messageMAC, []byte(expectedMacEncoded))
}

Account Section

Binding Process

This is the process of linking a 3rd party service to use OVO as a service. The process involves necessary user authorizations to authorize client actions on behalf of the user.

Lookup Phone No

This is to check the status of a user by sending their phone number to the below mentioned endpoint. OVO requires an Indonesian phone number to activate the account.

Endpoint:
/user/v2/account/lookup
User status definition :
Status Definition
ACTIVE User is an active OVO user ( Linkage Allowed)
ACTIVE_NO_PIN User is an active OVO user with NO PIN ( Linkage Allowed)
CAN_REGISTER User does not have OVO account ( Linkage Not Allowed)
BLOCKED_MSISDN User is blocked by OVO ( Linkage not allowed)
LINKED User is already linked with the partner.
Key Header
signature
Query Param
Key Value
phone <phone>
Response
ResponseCode Response Remarks
200
{
    "account": {
         "account_status": "ACTIVE"
    }
}
ACTIVE
ACTIVE_NO_PIN
CAN_REGISTER
BLOCKED_MSISDN
LINKED
400/500
{
    "error": {
         "code": "OV00003",
         "message": "User exceeds OTP attempt limit"
    }
}

The following is the specific error code for this API

HTTP Status Error Code Error Description
5xx OV00001 Internal Server Error
4xx OV00002 Bad/Incomplete Request
4xx OV00013 Client ID not valid/enabled
5xx OV00061 Something unexpected happen in server
4xx OV00001 HMAC signature does not match

Account Binding

This API will be used by Merchants to bind OVO Merchant ID and OVO User Phone No

Endpoint:
/{version}/registration-account-binding
Body Request
Parameter Data Type Mandatory Description
phoneNo String M Phone Number of the user
Format: 08xxxxxx
additionalInfo Object
e-mail Object O User email registered on merchant’s platform. For example [email protected]
merchantId String M Unique merchant identifier
device Object O Device related info
ID String O
Manufacturer String O
Model String O
OS String O
OsVersion String O
position Object O Position related info
Lat String O
Lon String O
Body Response
Parameter Data Type Description
responseCode String Internal OVO error/success codes
responseMessage String Response Description
referenceNo String Reference number to refer to the webview session, sending upon succesful binding. (refid)
linkageToken String Token to access webview
redirectUrl String URL to redirect user to OVO webview
additionalInfo Object
account Object
accountStatus String Current account status for the user’s OVO account
qparams Object
action String Action type
authType String Type of authentication
client-id String Registered client-id of the client
refid String Reference id to refer the webview session
phoneNumber String Phone number of the user
Sample Request
curl --location 'https://app.byte-stack.net/OVOSNAP/v2.0/oauth/account/registration-account-binding' \
--header 'X-Signature: 82ab1b8c03ff99ce07a70794dd3d2dd327d852887392069f1049cba3a5e5a1aeb77fbf556555a63614dbabeef490cf36c79248d9dbad07c0ca7f71a8d1f147f3f5ceb512286de0e7e239de2bc25afde011c12d75c373f60fe31bfcab7393959e77ebc2dd3340a5f3b489c4d6734f2a9bcf61004e0dbdc1b3cab24f5fa4f3faca96ddcff5b874c3c3c1ad5d4d247d005dd39d4c7b0214cc6e372c0cbd2173b8fcaaff10c5dbadad2225e602403430a8aa5a2d896dd2f97da58c8c58dcfc0fbd5cffd2d5f4bcd31f67a5b0917362c0e62b2e726443c7ff8179186b6b9d0410dea40ad57df4a05cae6ee798887715a890971e0b6d9945b50af21b040281cbe713e7ff12966342fd3b24858c7b0a4359f48f9a7a8b48aa81d82c86797b46e911415452e1195dea090f7bf3372aeb8bd1c23ed91b965692398584a666310a0652c48903e0d6552a1201425c3164a4552dbfb29b150c46b94a1815911b69d60e45f40ee6366d420acaaed6d3c9f5592a844671f990d134ae267816831440124ad397e27523e1706c70e6443fa611be61d466d63b3b63f8d0af58d99d18a99ab8125cdb3e0654417be99cf0096c0f60cc7b42aa13db7d2a1694cdf5352c04189efa0ef1ddb2497357ea5eea485565ca67e5f5be8ced72af38fa8a2d58b845a6903f884a508e68f1b72045dcf36f2b380cb075f30e7cb12c37ded77d698af9531046e42a' \
--header 'X-Timestamp: 2024-06-10T11:07:22.401+05:30' \
--header 'X-Partner-ID: oamerchantam' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: QAPayx-00003' \
--header 'Cookie: __cf_bm=ouBU3xu2XIakFHAkG_pQ7y9IGtPBSHapV_pajNQAC6A-1717997824-1.0.1.1-887CEo_bqNPKHjDVG9loagCFDvIdJL7wmCjArWo_2_heGT2yBmsR.93mDuGfOTayxB2Rk_8SO9DlQMkITC_rkw' \
--data '{
    "phoneNo": "080069696333",
    "merchantId": "117661"
}'
Sample Response
{
    "responseCode": "2000700",
    "responseMessage": "Success",
    "referenceNo": "2e24c3d4-d8c5-4de6-8bb1-520a2bf403f1",
    "linkageToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik9UQTFaalkzTlRZelltTTFOREkyWkRnNVpqTmpPRFpqTkdOalkySXlNMlkiLCJyYW5kb20iOiJNVEl4TVRRd05qVSIsInZlcnNpb24iOjF9.UaWK4z8ZO8cUkI19AnHEAMaej0kkLqNfj7LWZs33CAA",
    "redirectUrl": "https://webview.byte-stack.net/cellblockui/partner/activation",
    "additionalInfo": {
        "account": {
            "accountStatus": "LINKED"
        },
        "qParams": {
            "action": "otpLinkage",
            "authType": "2FA",
            "client-id": "oamerchantam",
            "phoneNumber": "08006969****",
            "refId": "2e24c3d4-d8c5-4de6-8bb1-520a2bf403f1",
            "skipTnc": "true"
        }
    }
}
Special Case

In case the user is blocked at our end (due to multiple wrong OTP/Pin attempts or other reasons) the error like below would be returned with HTTP code 403

{
     "data": null,
     "responseCode": "4030707",
     "responseMessage": "Card/User Blocked:Akun ini terkunci sementara. Silakan tunggu 1 jam lagi."
}

Partners can also add additional state parameter in the query parameters while opening the webview.

Opening Webview

Sample Webview URL
https://webview.byte-stack.net/cellblockui/partner/activation?authType=2FA&submissionType=redirect&destination=https://piswebblank.pis&action=otpLinkage&phoneNumber=08000080****&refId=a8afa060-c13b-4502-a19a-d53eefb435f7&client-id=oamerchantam&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik5qSmtOamt4TkdSak9XSTFOR0ZsTmpreU5ETTRZamczWldZMU0ySXdaR00iLCJyYW5kb20iOiJOalF3TmpRM09BIiwidmVyc2lvbiI6MX0.qZyXr5zVpiEtGqYbdPISHD9ylr8bOALEibR1jQTeAcg

The query param value must follow Account Binding API Response

Set/Verify PIN Response

After successful PIN verification, following query parameter will be received after redirection to partner domain

Body Response
Parameter Data Type Description
authCode String AuthCode sent to partner after successful user verification
state String State parameter(optional) as received from partner in query parameters while opening the OVO webview
displayMessage String Message presenting the final status of verification process
displayHeader String Can Ignore this
retryAttemptsLeft String Number of remaining PIN challenge maximum attemp
errorCode String Error Code
enable_redirection Boolean Redirection toggle to do redirection to callback URL
Sample Response

After successful PIN verification, the following query parameter will be received post redirection. The parameters relevant to the partner are mentioned in the table above.

response={
     "displayMsg":"SUCCESS",
     "displayHeader":"",
     "retryAttemptsLeft":4,
     "authCode":"1IdrZkhTTvi2AZ59Zz9usA",
     "state":"4b3fd750012a585c46aec444",
     "errorCode":"",
     "enable_redirection":false
}

In case error redirection is enabled for a partner, the following query parameter will be received after redirection to partner domain.

errorResponse={"code":"OV00006","state":"1234"}

errorResponse={
     "code":"OV00006",
     "state":"1234"
}

Notes : This required a special config from OVO and not all merchants used this, meaning this only returned for clients who expect a redirection in case of certain failures in webview

Error Code Error Description
OV00521 Too many wrong OTP
OV00003 Exceeds attempt limit, Not allowed request otp for 30min
OV00529 Your account is fully blocked
OV00527 Your account is blocked. Please try after 60min

Error redirection is the capability to redirect user back to client domain from OVO webview for terminating error use cases; by default error redirection is disabled meaning user will be redirected to client domain only after success user verification in OVO webview

Account Binding Error Code

HTTP status Error Code Status Message
500 5000701 FAILED Unknown Error
400 4000700 FAILED Bad Request
400 4000700 FAILED Invalid Merchant
404 4040716 FAILED Partner not found
404 4040701 FAILED Transaction Not found
429 4290700 FAILED Too Many Requests
403 4030705 FAILED Do Not Honor
500 5000702 FAILED Unknown Error
500 5000700 FAILED General Error
401 4010700 FAILED Unauthorized
401 4010701 FAILED Invalid Token
404 4040712 FAILED Invalid Bill/Virtual Account
400 4000701 FAILED Invalid Field Format
403 4030715 FAILED Transaction Not Permitted
404 4040715 FAILED Invalid OTP
403 4030718 FAILED Inactive Card/Account/Customer
405 4050700 FAILED Requested Function Is Not Supported
405 4050701 FAILED Requested Operation Is Not Allowed
403 4030707 FAILED Card/User Blocked

Access Token Request (B2B2C)

This API call is required to be made using the linkageToken received from Account Creation/Binding Response. Use the token in Authorisation header

Endpoint:
/{version}/access-token/b2b2c
Body Request
Parameter Data Type Mandatory Description
authCode String(256) C AuthCode received after PIN challenge(Conditional)
refreshToken String(512) C

Refresh token to get a new accessToken where the User doesn't need to provide the consent again.

mandatory if grantType = refresh_token.

Refresh Token should be less than access token validity and will be managed by the PJP's application to generate a new access_token(Conditional)
grantType String M Apply token request key type, can be authorization_code or refresh_token
Body Response
Parameter Data Type Description
responseCode String Internal OVO error/success codes
responseMessage String Response Description
accessToken String(2048) A string representing an authorization issued to the client that used to access protected resources
accessTokenExpiryTime String Datetime of token expiration. Format: ISO8601
tokenType String The access token type provides the client with the information required to successfully utilize the access token to make a protected resource request (along with type-specific attributes) Token Type Value: "Bearers”: includes the access token string in the request "Mac": issuing a Message Authentication Code (MAC) key together with the access token that is used to sign certain components of the HTTP requests
refreshToken String A random string that can be used by specific client to get a refreshed accessToken to prolong the access to the User's resources.
refreshTokenExpiryTime String Time when the refreshToken will be expired. Refresh Token should be less than access token validity and will be managed by the PJP's application to generate a new access_token
additionalInfo Object
ilp String Ovo identifier for a particular linkage. Can be later used for unlink, troubleshooting/debugging issues, sharing information, translating to OVO channel user ID etc with ovo.
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/access-token/b2b2c' \
--header 'X-Signature: 2b87b4157d56b798d954ab6bc1a02d48b9825d5a85237757da9463842d4c2abe4790c1bcaf9239f577d08e7ec4fed2ca4e76f50abf8f5d04be36308983ae78ca760954fd43421cdd9715bd47ccadab16441c7d030378fd78c011fc6b9cdf3c9f1cd6687b550b682c895b86e9c7556e5d8381a69d32327cbeaf8e7600da224bdd5d4d0d96c43a9f2dc62cc9fe6fc8a61c60f9d1193b73a4bba0750e5b218ed94eedd8fa1362f7cffdf48c64be0f6f353c3ef909390fa8df2cd6d0b2ecb93394ed4c80ed660a3909b375c9c04f56bacf064a151246bb333dc51c91f1f1a160c2ed3f5da3d91e5c3ed5f2f9cc9e31af840074611ba44d7c9d1604d0520e6813757a0745f86e6a230975b0b648c9a1fef26d8ef33518723b12b574423546dc871ad37f6ac7349a79db98385f01dcc3ffe810ddd6cc59a104695cf48ee1a7d28721fad3f6c825b837a4b64fbc49c65a318e415ca50cd5310341fc382a31a5ed1cc880b3753c3b6920fb5fe2befe5cd906552ce649e803aac0f22041b2d59983325bb425438dd4b9bdb4a0ab21820b65ef23f1a648819bbe60e0479e1e92f3f26b699aba973298fd7bf386d90473fc2b5905904e38b5e5b3c979c2d7e9cc7ccd89b6d529cde09f128e718ce52cb7097268de0ae37020f212af23c8156d4df29e94f0ae607ddf65d85a9ff14dff0c6aa06021304271ab2c888ee1524f79e586b9af4723' \
--header 'X-Timestamp: 2022-08-04T10:25:13.511+07:00' \
--header 'X-Client-Key: oamerchantam' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik16WmlaRFUxTmpGbVkyUTJORFkyWldKa1pEZzNNVEJoWmpKa01EZGlORFkiLCJyYW5kb20iOiJOalF3TlRZeU5nIiwidmVyc2lvbiI6MX0.njOfzmHQ9-vk93NmqF1xFBU82hfy2XBYMoNODrQY-ss' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: binding1-1' \
--data-raw '{
  "grantType" : "authorization_code",
  "authCode" : "Z269GYi7QYWCWOiTcLeC3A"
}'
  • This API will serve the functionality of both refreshing the old tokens and generating new tokens using authCode
  • If grantType is authorization_code API generates new access token and Refresh Token
  • If grantType is refresh_token API refreshes the old tokens without any need for user authorisation
Sample Response
{
   "responseCode": "2007400",
   "responseMessage": "Success",
   "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik5HSmpPRE5oWkRBeVlUQTROREpqT1dFell6Z3paVGxqTXpoak16azBZVFkiLCJyYW5kb20iOiJOalF3TlRZMU9BIiwidmVyc2lvbiI6MX0.8gQ0LLb7THupNBoJz0Utqc6DH7XpDVSZYiAs__8nIaI",
   "tokenType": "Bearer",
   "accessTokenExpiryTime": "2023-08-04T03:25:14Z",
   "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik1UTmhPVGsyWXpVNU4yWXhOR0psWkdJME5XWXpZemxsTVRnMVpqSTJOVFUiLCJyYW5kb20iOiJOalF3TlRZMU53IiwidmVyc2lvbiI6MX0.GKM-C2dLlpNUN4JqkvbmB11nt4oiaZ7XG_KcvfJoVAI",
   "refreshTokenExpiryTime": "",
   “additionalInfo” : {
        “Ilp” : “ovo.user.testMerchantBISnap.ADABMQAwATACMwIyAAEACQAFAgAHAg”
   }
}

ILP id is a unique identifier for OVO to identify the user for a particular partner linkage. Partners need to store the ILP id at their end which could be later used to share information relating to a user

As per BI requirements, the token expiry for the access token received in this API should be 15 days

Refresh Access Token Logic

Partner can refresh the expired tokens, call the API B2B2C Access Token with grantType = refresh_token

For responseCode: OV00502 or 401XX00, partner are required to regenerate access tokens

{
   "data": {
      "actionables": [
        {
          "pinWebviewURL": "https://webview.byte-stack.net/cellblockui/v2/paymentPin",
          "qParams": {
            "action": "regeneratePayment",
            "client-id": "testMerchantBISnap"
          },
          "token": "PaymentToken"
      }
      ],
      "error": {
        "code": "OV00502",
        "message": "Unauthorized access"
      }
   },
   "responseCode": "401XX01",
   "responseMessage": "General unauthorized error (No Interface Def, API is Invalid, Oauth Failed, Verify Client Secret File, Client Forbidden Access API, Unknown Client, Key not Found)"
}

If data.error.code == OV00502 or 401XX01, we expect clients to refresh the old tokens.
This reactive approach is to be applied for refreshing all types of tokens

How to Handle Token Expired

For expired token condition, please use below flow

  • if access token invalid/expired then call refresh token API
  • if refresh token API get success => use new access token to continue the process/transaction
  • if refresh token API get error (401) unathorized => revoke all token for this customer( mark customer as unlinked)
B2B2C Error Code
HTTP status Error Code Status Message
500 5007401 FAILED Unknown Error
400 4007400 FAILED Bad Request
404 4047416 FAILED Partner not found
404 4047401 FAILED Transaction Not found
429 4297400 FAILED Too Many Requests
403 4037405 FAILED Do Not Honor
500 5007402 FAILED Unknown Error
500 5007400 FAILED General Error
401 4017400 FAILED Unauthorized
401 4017401 FAILED Invalid Token
404 4047412 FAILED Invalid Bill/Virtual Account
400 4007401 FAILED Invalid Field Format
403 4037415 FAILED Transaction Not Permitted
404 4047415 FAILED Invalid OTP
403 4037418 FAILED Inactive Card/Account/Customer
405 4057400 FAILED Requested Function Is Not Supported
405 4057401 FAILED Requested Operation Is Not Allowed
403 4037407 FAILED Card/User Blocked

Generate System Token (B2B)

This API will be used by Merchants to trigger Refund API

Partner with client-specific use cases (non-user-related use-case) can use the below API to generate system tokens.

Before generating the token, partners are requested to address this as a requirement, so OVO can configure the necessary.

As per BI requirements, the token expiry for B2B token should be 15 minutes

Endpoint:
/{version}/access-token/b2b
Body Request
Parameter Data Type Mandatory Description

grant_type

String

M

"client_credentials”: The client can request an access token using only its client credentials (or other supported means of authentication) when the client is requesting access to the protected resources under its control

Body Response
Parameter Data Type Description
responseCode string Response Code
responseMessage string Response Message
accessToken string A string representing an authorization issued to the client that used to access protected resources
tokenType string The access token type provides the client with the information required to successfully utilize the access token to make a protected resource request (along with type-specific attributes)
Token Type Value:
  • "Bearers”: includes the access token string in the request
  • "Mac": issuing a Message Authentication Code (MAC) key together with the access token that is used to sign certain components of the HTTP requests
expiresIn string Time in seconds
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v1.0/access-token/b2b' \
--header 'x-client-key: oamerchantam' \
--header 'Content-Type: application/json' \
--header 'X-Signature: d34174d14808b8ea3c7f631ba18c22ef83beb040d080e972292920c5ea0a255220f8d53623da5e2bacbf2cda96e1c57eae7e2ad25328afaefcaa01b8176492455b2498fedfc9a0e76e0d759a0ffb6d2f8cb7f2ee0f63f86510520b145e9459057c794e4fcd5e540cc3e2d68a95c58396690356da2f7e6fc68e4a7085a7ea3ef00f4c1a8fac1c1513bf5294f23145ee3f680251fc41a953058603c547a5097872d5d38f64e1cbdf1899e54df6ab27cc571a1f3b45e403a0e6a1dfcffd346440270f93943ffc4acf49a890157605855591e66f0a60c8a7d39576a2764794c4e26c67e2e50f67fb279808a0579b311b3e2a55b872aa04a82e266fed91b3984fd9336eee161775324f0a138320505123bea26fcc7465fb1cc6a47cf81308a8540eb1e2fa3e18e575befea76f244bdc4b65baf28e19b46bde6d4fe6fdf0323400a27e9761e6f86650dbdf6273b535abc15ad0f3f7c441bd9a434c173e474f13b578eab93329001b7baed333fa39ee4f2c5445d4d71f076c58251198959cc4ce84bf65e4fd8457e993040d29809f94a8c5d4bc3f975491c0849a8f92f8cfb4876805353decbf90050993b43c0ef72e25cb43f6bb0769ca029c711b5e872f12f972fd42606eebe613ed34774128b1f5bf607df5c8e8a2cd0a820bd5c406c1ada033537b06248636750c3746bdebe0fca7ba5d3cff8c70de6fc0392a030b52ce152b74a6' \
--header 'x-Timestamp: 2022-08-04T10:29:29.190+07:00' \
--header 'X-EXTERNAL-ID: binding1-1' \
--data-raw '{
   "grantType" : "client_credentials"
}'
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v1.0/access-token/b2b' \
--header 'x-client-key: oamerchantam' \
--header 'Content-Type: application/json' \
--header 'X-Signature: d34174d14808b8ea3c7f631ba18c22ef83beb040d080e972292920c5ea0a255220f8d53623da5e2bacbf2cda96e1c57eae7e2ad25328afaefcaa01b8176492455b2498fedfc9a0e76e0d759a0ffb6d2f8cb7f2ee0f63f86510520b145e9459057c794e4fcd5e540cc3e2d68a95c58396690356da2f7e6fc68e4a7085a7ea3ef00f4c1a8fac1c1513bf5294f23145ee3f680251fc41a953058603c547a5097872d5d38f64e1cbdf1899e54df6ab27cc571a1f3b45e403a0e6a1dfcffd346440270f93943ffc4acf49a890157605855591e66f0a60c8a7d39576a2764794c4e26c67e2e50f67fb279808a0579b311b3e2a55b872aa04a82e266fed91b3984fd9336eee161775324f0a138320505123bea26fcc7465fb1cc6a47cf81308a8540eb1e2fa3e18e575befea76f244bdc4b65baf28e19b46bde6d4fe6fdf0323400a27e9761e6f86650dbdf6273b535abc15ad0f3f7c441bd9a434c173e474f13b578eab93329001b7baed333fa39ee4f2c5445d4d71f076c58251198959cc4ce84bf65e4fd8457e993040d29809f94a8c5d4bc3f975491c0849a8f92f8cfb4876805353decbf90050993b43c0ef72e25cb43f6bb0769ca029c711b5e872f12f972fd42606eebe613ed34774128b1f5bf607df5c8e8a2cd0a820bd5c406c1ada033537b06248636750c3746bdebe0fca7ba5d3cff8c70de6fc0392a030b52ce152b74a6' \
--header 'x-Timestamp: 2022-08-04T10:29:29.190+07:00' \
--data-raw '{
   "grantType" : "client_credentials" }'
Sample Response
{
   "responseCode": "2007300",
   "responseMessage": "Success",
   "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik56UTJPV0pqWWpSallXVmhOREEyTkdFM1lXSmhPVFF3T1dZek9EZG1PVFkiLCJyYW5kb20iOiJOalF3TlRZMk1nIiwidmVyc2lvbiI6MX0.JunB-LHxNnkGsSKb6P72iMaztZFuRyTuxGCZevrC6cE",
   "tokenType": "Bearer",
   "expireIn": "300",
}
Refresh System token Logic

There are two ways in which a partner can refresh the expired tokens

  1. Proactive approach: Partner proactively generates a new B2B token every 15 min, just when the old token is about to expire. This requires partner to be aware of the expiry beforehand
  2. Reactive approach: Partner reacts to the responseCode returned by OVO. If responseCode corresponding to expiredToken is received while using the B2B token, partner regenerates the tokens using the above API.
  3. Refer to Refresh Access Token Logic for more information on how to regenerate token using reactive approach
  4. Merchant should refrain from generating multiple B2B tokens before the expiry. A single token should be created and reused throughout the expiry
B2B Error Code
HTTP status Error Code Status Message
500 5007301 FAILED Unknown Error
400 4007300 FAILED Bad Request
404 4047316 FAILED Partner not found
404 4047301 FAILED Transaction Not found
429 4297300 FAILED Too Many Requests
403 4037305 FAILED Do Not Honor
500 5007302 FAILED Unknown Error
500 5007300 FAILED General Error
401 4017300 FAILED Unauthorized
401 4017301 FAILED Invalid Token
404 4047312 FAILED Invalid Bill/Virtual Account
400 4007301 FAILED Invalid Field Format
403 4037315 FAILED Transaction Not Permitted
404 4047315 FAILED Invalid OTP
403 4037318 FAILED Inactive Card/Account/Customer
405 4057300 FAILED Requested Function Is Not Supported
405 4057301 FAILED Requested Operation Is Not Allowed
403 4037307 FAILED Card/User Blocked

Unbinding

To allow a user to unlink/unbind an existing binding with OVO, client needs to engage user with necessary authorisations before asking OVO to unlink the account.

To allow a user to unlink/unbind an existing binding with OVO, the client needs to engage the user with necessary authorizations before asking OVO to unlink the account.

If multiple bindings are allowed for a client, all the bindings for a user through such a client (for instance GooglePlay) will be unbinded.

Endpoint:
/{version}/registration-account-unbinding
Body Request
Parameter Data Type Mandatory Description
partnerReferenceNo String M phone Number
merchantId String M Unique merchant identifier
Body Response
Parameter Data Type Description
responseCode String Internal OVO error/success codes
responseMessage String Response Description
referenceNo String Reference number to refere to the webview session, sending upon successful binding. (refid)
unlinkResult String The status of the unbinding request
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/oauth/account/registration-account-unbinding' \
--header 'X-Signature: a9f58d3d78bd829cee92af8a65f07ae7a74f79498212204dcf4281bd9c26a08368036c83fab508930cb44355209ab3c4eb84cdd5c02e747dbbce00226b874d38' \
--header 'X-Timestamp: 2022-08-04T07:43:27.257+07:00' \
--header 'X-Partner-ID: oamerchantam' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik9XVTFZMlpoTVdJelpHTXdOREprTXprNVpqbGtNbVZpTm1VMk1XSXpNVFEiLCJyYW5kb20iOiJOalF3TmpRNE1RIiwidmVyc2lvbiI6MX0.AQfIXOJ39MEGdXWkiNjX7TL_64sTNOgWrtWqjceLWUg' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: binding1-1' \
--data-raw '{
   "partnerReferenceNo" : "080000809420",
   "merchantId" : "118631"
}'
Sample Response
{
   "responseCode": "2000900",
   "responseMessage": "Success",
   "referenceNo": "796a2b38-1bcd-46d4-8c43-aadec3c6778c",
   "unlinkResult": "Success",
   "additionalInfo": null
}
Account Unlink Error Code
HTTP status Error Code Status Message
500 5000901 FAILED Unknown Error
400 4000900 FAILED Bad Request
404 4040916 FAILED Partner not found
404 4040901 FAILED Transaction Not found
429 4290900 FAILED Too Many Requests
403 4030905 FAILED Do Not Honor
500 5000902 FAILED Unknown Error
500 5000900 FAILED General Error
401 4010900 FAILED Unauthorized
401 4010901 FAILED Invalid Token
404 4040912 FAILED Invalid Bill/Virtual Account
400 4000901 FAILED Invalid Field Format
403 4030915 FAILED Transaction Not Permitted
404 4040915 FAILED Invalid OTP
403 4030918 FAILED Inactive Card/Account/Customer
405 4050900 FAILED Requested Function Is Not Supported
405 4050901 FAILED Requested Operation Is Not Allowed
403 4030907 FAILED Card/User Blocked


Payment Section

Balance Inquiry

This API will be used by Merchants to get OVO Balance for a specific user

Body Request
Parameter Data Type Mandatory Description
partnerReferenceNo String O Transaction identifier on service consumer system
bankCardToken String O Card token for payment
accountNo String M OVO Phone No
[balanceType] Array of Object O ["CASH","POINTS"] Please use capital letter
additionalInfo Object O
deviceId String O
channel String O

Body Response

Parameter Data Type Mandatory Description
responseCode String M Response code
responseMessage String M Response description
referenceNo String O Transaction identifier on service provider system. Must be filled upon successful transaction
partnerReferenceNo String O Transaction identifier on service consumer system
accountNo String O Registered account number
name String O Customer account name
[accountInfos] Array of Object
balanceType String O Account type name
amount Object O
value String (ISO4217) M Net amount of the transaction. If it’s IDR then the value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00
currency String M Currency
floatAmount Object O
value String (ISO4217) C Amount of deposit that is not effective yet (due to holiday, etc.). If it’s IDR then the value includes 2 decimal digits. e.g. IDR 50.000,- will be placed with 50000.00 Must be filled if the floatAmout data exist
currency String C Currency Must be filled if the flagAmout data exist
holdAmount Object O
value String (ISO4217) C Hold amount that cannot be used. If it’s IDR then the value includes 2 decimal digits. e.g. IDR 20.000,- will be placed with 20000.00
Must be filled if the floatAmout data exist
currency String C Currency Must be filled if the floatAmout data exist
availableBalance Object O
value String (ISO4217) C Account balance that can be used for financial transaction
Must be filled if the availableBalance data exist
currency String C Currency Must be filled if the availableBalance data exist
ledgerBalance Object O
value String (ISO4217) C Account balance at the beginning of each day
Must be filled if the ledgerBalance data exist
currency String C Currency
currentMultilateralLimit Object O
value String (ISO4217) C Credit limit of the account / plafon
Must be filled if the currentMultilateralLimit data exist
currency String C Currency Must be filled if the currentMultilateralLimit data exist
registrationStatusCode String O Customer registration status
status String O Account Status
1 = Active Account
2 = Closed Account
4 = New Account
6 = Restricted Account
7 = Frozen Account
9 = Dormant Account
additionalInfo Object O
deviceId String O
channel String O
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/balance-inquiry' \
--header 'X-PARTNER-ID: oamerchantam' \
--header 'X-TIMESTAMP: 2022-08-04T05:10:39.126+07:00' \
--header 'X-SIGNATURE: a833036113fcd913f3f304a0cc6548ffa8277a611a469db8404e2942fb2165175f545fd253ec06b8ebfe1cfc2ad5c24229ec0bd85024b63cdcf2aec598afbfa7' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik5HSmpPRE5oWkRBeVlUQTROREpqT1dFell6Z3paVGxqTXpoak16azBZVFkiLCJyYW5kb20iOiJOalF3TlRZMU9BIiwidmVyc2lvbiI6MX0.8gQ0LLb7THupNBoJz0Utqc6DH7XpDVSZYiAs__8nIaI' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: balance4' \
--data-raw '{
    "partnerReferenceNo":"ccc",
    "bankCardToken":"6d7963617264746f6b656e",
    "accountNo":"080000809420",
    "balanceType":["Cash","Points"],
    "additionalInfo": {
       "deviceId":"12345679237",
       "channel":"mobilephone"
    }
}'
Sample Response
{
   "responseCode": "2001100",
   "responseMessage": "Request has been processed successfully",
   "accountInfos": [
      {
        "balanceType": "CASH",
        "amount": {
          "value": "19992334.00",
          "currency": "IDR"
        },
        "floatAmount": {
          "value": "19992334.00",
          "currency": "IDR"
        },
        "holdAmount": {
          "value": "19992334.00",
          "currency": "IDR"
        },
        "availableBalance": {
          "value": "19992334.00",
          "currency": "IDR"
        },
        "ledgerBalance": {
          "value": "19992334.00",
          "currency": "IDR"
        },
        "currentMultilateralLimit": {
          "value": "19992334.00",
          "currency": "IDR"
        }
      },
      {
        "balanceType": "POINTS",
        "amount": {
          "value": "1000000.00",
          "currency": "IDR"
        },
        "floatAmount": {
          "value": "1000000.00",
          "currency": "IDR"
        },
        "holdAmount": {
          "value": "1000000.00",
          "currency": "IDR"
        },
        "availableBalance": {
          "value": "1000000.00",
          "currency": "IDR"
        },
        "ledgerBalance": {
          "value": "1000000.00",
          "currency": "IDR"
        },
        "currentMultilateralLimit": {
          "value": "1000000.00",
          "currency": "IDR"
        }
      }
   ]
}
Response Code
HTTP status Error code Status Message
200 2001100 SUCCESS Request has been processed successfully
400 4001100 FAILED Bad Request. the accountNo allowed max length is 16
400 4001100 FAILED Bad Request. the accountNo field is required
400 4001100 FAILED Bad Request. invalid format accountNo field
409 4091100 FAILED Conflict

Generate SingleUseToken

This endpoint is able to give you SingleUseToken .The token returned will depend on the <auth_code> given from the PIN WebView after successful PIN verification.

This will be used only for Direct Debit API

Request
Method URL
POST /user/v1/oauth/token
Header
Key
Authorization: Bearer <access_token>
signature
Query Param
Key Value
grantType authorization_code
code <auth_code>
Response
Status Response
grantType
{
   "tokens" : [
      {
        "name" : "SingleUseToken",
        "accessToken" :
"f4ed830026bb11ae91cc4ba8f2e1ea84502073a59ee1e"
      }
   ]
}
code
{
   "error": {
      "code": "OV00002",
      "message": "Bad Request"
   }
}

Direct Debit

This API will be used by Merchants to initiate Payment to OVO with Cash and Points

Body Request
Parameter Data Type Mandatory Description
partnerReferenceNo String M Transaction identifier on service consumer system
bankCardToken String O
merchantId String M Merchant identifier that is unique per each merchant
terminalId String O Terminal ID
journeyId String O Merchant ID
subMerchantId String O Sub-merchant ID
amount Object O
value String M Net amount of the transaction. If it’s IDR then the value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00
currency String M Currency
urlParam Object O
url String M The URL
type String M URL Type PAY_RETURN/PAY_NOTIFY
isDeeplink String M Whether the URL is a deep link URL or not Y/N
externalStoreId String O Store ID to indicate to which store this payment belongs to.
validUpTo String O The time when the payment will be automatically expired. ISO 8601
pointOfInitiation String O used for getting more info regarding source of request of the user
feeType String O to whom the fee will be charged 1. OUR Fee is charged to the sender (default) 2. BEN Fee is charged to the recipient 3. SHA|1000 Fee is shared between sender and recipient, with sender is charged Rp 1.000,00 and the recipient will be charged the rest
disabledPayMethods String O Payment method(s) that cannot be used for this payment
payOptionDetails Object M
payMethod String M [“CASH”,”POINTS”] Please use capital letter
payOption String M Payment option which shows the provider of this payment e.g. CREDIT_CARD_VISA
transAmount Object M
value String M Transaction amount that will be paid using this paymentmethod If it’s IDR then value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00
currency String M Currency
feeAmount Object O
value String (ISO 4217 ) C Fee amount that will be paid using this payment method If it’s IDR then value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00Must be filled if the feeAmount data exist
currency String C CurrencyMust be filled if the feeAmount data exist
cardToken String O Card token used for this payment
chargeToken String M Use default value “OVO”
merchantToken String O Merchant token used for this payment
additionalInfo Object O Additional information
deviceId String O
channel String O
additionalInfo Object O Additional information
deviceId String O
channel String O
subTransactionType String C Conditional for Auto Debit Feature. Please refer to below section for Auto Debit
issuingMerchantName String O
notes String O

Auto Debit

The feature is used to allow Direct Debit API to be used without PIN process, e.g. in the recurring payment process. In the request payload, to allow this process, the field “subTransactionType” must be present. Currently supported field value:

  1. “MANUAL” : this is the default value for Direct Debit API, which means the PIN process will be executed for every transaction (i.e. API Call). If the “subTransactionType” is not present in the request payload, this default value will be used
  2. “AUTO” : this is the value, to be set, for Direct Debit API that will not check for PIN process, which means the transaction will be executed accordingly
  3. This capability needs OVO special approval (OVO Security Team) and needs to be configured first by OVO to whitelist the merchant ID
Body Response
Parameter Data Type Mandatory Description
responseCode String M Response Code
responseMessage String M Response Description
referenceNo String C Transaction identifier on service provider system. Must be filled upon successful transaction
partnerReferenceNo String O Transaction identifier on service consumer system
appRedirectUrl String O Returns an URL scheme to the PJP AIS payment page in native app.
webRedirectUrl String O Returns a universal link to PJP AIS payment page. This link is recommended when the Client is unable to implement a check for whether PJP AIS app is installed on the user’s device before redirect.
additionalInfo Object O Additional information
deviceId String O
channel String O
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/debit/payment-host-to-host'' \
--header 'X-PARTNER-ID: oamerchantam' \
--header 'X-TIMESTAMP: 2022-08-04T05:35:32.848+07:00' \
--header 'X-SIGNATURE: b31edf113da4e6c7a0266e3417a05fad979b6a4ad803b89e9708ffe07447e317fa321b508f4d312a5beac1c40123bcf279ba4e5ab4506e38db6f42c919ef250d' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik1HWmpNemd3TVRaaU5UZzNOREJsTTJFNU5ERXhaVEl3TmpaaE1UTXhaV0kiLCJyYW5kb20iOiJOalF3TmpRd05nIiwidmVyc2lvbiI6MX0.tq0rURYiLDfl5JfeNP4vGHjzR56UUaZ6u-GAZAj4H7s' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: payment6' \
--data-raw '{
    "partnerReferenceNo": "dwiyogapoint12",
    "chargeToken": "OVO",
    "merchantId": "117661",
    "amount": {
       "value": "10000.00",
       "currency": "IDR"
    },
    "payOptionDetails":
    [{
       "payMethod": "CASH",
       "payOption": "OVO",
       "transAmount": {
             "value": "10000.00",
             "currency": "IDR"
          }
       }
    ]

}'
Sample Response 1st call using Access Token
{
   "responseCode": "2025400",
   "responseMessage": "Transaction still on process",
   "referenceNo": "",
   "partnerReferenceNo": "dwiyogapoint12",
   "webRedirectUrl": "https://webview.byte-stack.net/cellblockui/v2/paymentPin",
   "additionalInfo": {
      "clientTxnId": "dwiyogapoint12",
      "action": "payment",
      "client-id": "oamerchantam"
   }
}

Opening Webview for Direct Debit 1st API Call

Sample Webview URL
https://piswebblank.pis/?response={"displayMsg":"SUCCESS","displayHeader":"","retryAttemptsLeft":4,"authCode":"lGG73abGSdaz6BEGw8VHnw","errorCode":"","enable_redirection":false}
Sample Response 2nd call using single use token
{
   "responseCode": "2005400",
   "responseMessage": "Request has been processed successfully",
   "referenceNo": "dwiyogapoint12",
   "partnerReferenceNo": "dwiyogapoint12",
   "webRedirectUrl": ""
}
Response Code
HTTP status Error code Status Message
200 2005400 SUCCESS Request has been processed successfully
400 4005400 FAILED Bad Request. the partnerReferenceNo field is required
500 5005400 FAILED General Error. request not valid
400 4005400 FAILED Bad Request. the merchantId field is required
400 4005400 FAILED Bad Request. the amount field is required
400 4005400 FAILED Bad Request. can't convert {amount} to decimal: too many .s
400 4005400 FAILED Bad Request. request not valid
500 5005400 PENDING General Error. Transaction pending
403 4035414 FAILED Insufficient Funds
403 4035401 FAILED Feature Not Allowed
400 4005400 FAILED Bad Request. the payOption is not valid
409 4095400 FAILED Conflict

Direct Debit Refund

This API will be used by Merchants to initiate Payment Refund to OVO

Body Request
Parameter Data Type Mandatory Description
merchantId String O Merchant identifier that is unique per each merchant
subMerchantId String O Sub-merchant ID
originalPartnerReferenceNo String M Original transaction identifier on service consumer system
originalReferenceNo String O Original transaction identifier on service provider system
originalExternalId String O Original Customer Reference Number
partnerRefundNo String M Reference Number from PJP AIS for the refund
refundAmount Object M
value String (ISO 4217) M Fee amount that will be paid using this payment method If it’s IDR then value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00
currency String M Currency
externalStoreId String O External Store ID
reason String O Refund reason.
additionalInfo Object M JSON object which contains array of the detail refund amount (per Source of Fund)
Example:
{
  "sof": [
    {
    "accType": "CASH",
    "amt": {
      "value": "1000.00",
      "currency": "IDR"
     }
    },
    {
    "accType": "POINTS",
    "amt": {
      "value": "1000.00",
      "currency": "IDR"
     }
    }
  ]
}
accType String M Current supported values: "CASH" for cash refund, "POINTS" for point refund
amt String M Detail amount
value Numeric M Refund amount
currency String M Currently supported value: "IDR"
Body Response
Parameter Data Type Mandatory Description
responseCode String M Response Code
responseMessage String M Response Description
originalPartnerReferenceNo String O Transaction identifier on service provider system. Must be filled upon successful transaction
originalReferenceNo String C Transaction identifier on service consumer system
originalExternalId String O Original Customer Reference Number
partnerTrxId String O Partner Transaction ID
refundNo String M Reference Number
partnerRefundNo String M Reference Number from PJP AIS for the refund.
refundAmount Object M
value String (ISO 4217) M Fee amount that will be paid using this payment method. If it’s IDR then value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00 Must be filled if the refundAmount data exist
currency String M Currency Must be filled if the refundAmount data exist
refundTime String M Refund time. ISO 8601
additionalInfo Object M JSON object which contains array of the detail refund amount (per Source of Fund)
Example:
{
  "sof": [
    {
    "accType": "CASH",
    "amt": {
      "value": "1000.00",
      "currency": "IDR"
     }
    },
    {
    "accType": "POINTS",
    "amt": {
      "value": "1000.00",
      "currency": "IDR"
     }
    }
  ]
}
sof Array M Array which contains accType and amt
accType String M Current supported values:
  1. "CASH" for cash refund
  2. "POINTS" for point refund
amt Object M Detail amount
value Numeric M Refund amount
currency String M Currently supported value: "IDR"
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/debit/refund' \
--header 'X-PARTNER-ID: oamerchantam' \
--header 'X-TIMESTAMP: 2022-08-04T05:36:46.090+07:00' \
--header 'X-SIGNATURE: f8c6cadafdd480412a7cf6886068703d39e6e8f34ebfde87c3e7fcd5382a603be1abc8a0695f5c7d74e157d67fe9f205dc832aef73738d1c7f2b2795137dfda1' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik5HSmpPRE5oWkRBeVlUQTROREpqT1dFell6Z3paVGxqTXpoak16azBZVFkiLCJyYW5kb20iOiJOalF3TlRZMU9BIiwidmVyc2lvbiI6MX0.8gQ0LLb7THupNBoJz0Utqc6DH7XpDVSZYiAs__8nIaI' \
--header 'Content-Type: application/json' \
--header 'X-EXTERNAL-ID: dwiyoga12' \
--data-raw '{
   "originalPartnerReferenceNo": "dwiyogapoint12",
   "partnerRefundNo": "refund-dwiyogapoint12",
   "refundAmount": {
      "value": "1000.00",
      "currency": "IDR"
   },
   "additionalInfo": {
      "sof": [
         {
            "accType": "CASH",
            "amt": {
               "value": "1000.00",
               "currency": "IDR"
            }
         }
      ]
   }
}'
Sample Response
{
   "responseCode": "2005800",
   "responseMessage": "Request has been processed successfully",
   "originalReferenceNo": "dwiyogapoint12",
   "partnerRefundNo": "refund-dwiyogapoint12",
   "refundAmount": {
      "value": "1000.00",
      "currency": "IDR"
   },
   "refundTime": "2022-08-04T10:36:46Z"
}
Response Code
HTTP status Error code Status Message
200 2005800 SUCCESS Request has been processed successfully
400 4005800 FAILED Bad Request. the originalPartnerReferenceNo field is required
404 4045801 FAILED Transaction Not Found. ledger system resource not found
400 4005800 FAILED Bad Request. the partnerRefundNo field is required
400 4005800 FAILED Bad Request. the refundAmount.amount field is required
400 4005800 FAILED Bad Request. can't convert {amount} to decimal: too many .s
400 4005800 FAILED Bad Request. request not valid
404 4045811 FAILED Customer Not Match
400 4005802 FAILED Exceeds Transaction Amount Limit
400 4005815 FAILED Transaction Not Permitted
403 4035802 FAILED Exceeds Transaction Amount Limit. the amount requested exceeds refundable amount
409 4095800 FAILED Conflict

Direct Debit Inquiry Status

This API will be used by Merchants to get the specific transaction Status

Body Request
Parameter Data Type Mandatory Description
originalPartnerReferenceNo String M Original transaction identifier on service consumer system
originalReferenceNo String O Original transaction identifier on service provider system
originalExternalId String O Original ExternalID on header message
serviceCode String M Transaction type indicator (service code of the original transaction request)
Code 54 (Direct Debit Service Code)
If it is not 54, return 400
transactionDate String O transaction date : ISO 8601
amount Object O
value String (ISO 4217) M Net amount of the transaction. If it’s IDR then the value includes 2 decimal digits. e.g. IDR 10.000,- will be placed with 10000.00
currency String M Currency
merchantId String O Merchant identifier that is unique per each merchant
subMerchantId String O Sub-merchant ID
externalStoreId String O External Store ID for merchant
additionalInfo Object O
deviceId String O
channel String O
Body Response
Parameter Data Type Mandatory Description
responseCode String M Response Code
responseMessage String M Response Description
originalPartnerReferenceNo String O Transaction identifier on service provider system. Must be filled upon successful transaction
originalReferenceNo String C Transaction identifier on service consumer system
originalExternalId String O Original ExternalID on header message
serviceCode String M Transaction type indicator (service code of the original transaction request)
latestTransactionStatus String M 00 - Success 01 - Initiated 02 - Paying 03 - Pending 04 - Refunded 05 - Canceled 06 - Failed 07 - Not found
transactionStatusDesc String O Description status transaction
originalResponseCode String M Response code
originalResponseMessage String O Response description
sessionId String O Transaction invoice ID
requestID String O Transaction invoice ID
refundHistory Array of Object C If refund data exist, then the field must be filled
refundNo String C Transaction Identifier on Service Provider System
partnerReferenceNo String C Reference Number from PJP AIS for the refund
refundAmount Object
value String (ISO 4217) C Net amount of the refund
currency String C Currency
refundStatus String C 00 - Success 03 - Pending 06 - Failed
refundDate String C (ISO 8601) transaction date : dd-MMyyyy (Mandatory ) HH:mm:ss (Optional)
reason String C Refund reason
paidTime String C transaction date : ISO 8601
additionalInfo Object O
deviceId String O
channel String O
Sample Request
curl --location --request POST 'https://app.byte-stack.net/OVOSNAP/v2.0/debit/status' \
--header 'X-PARTNER-ID: oamerchantam' \
--header 'X-TIMESTAMP: 2022-08-04T05:37:27.114+07:00' \
--header 'X-SIGNATURE: 65028204f6097a11d8d8456f1a10d4a27730a9c45c1f547f5547495bc93ee9071a048e70b64cf6e42b040eaa9ad18fbd1d666ba84051e70965359a305206f1a1' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik5HSmpPRE5oWkRBeVlUQTROREpqT1dFell6Z3paVGxqTXpoak16azBZVFkiLCJyYW5kb20iOiJOalF3TlRZMU9BIiwidmVyc2lvbiI6MX0.8gQ0LLb7THupNBoJz0Utqc6DH7XpDVSZYiAs__8nIaI' \
--header 'Content-Type: application/json' \
--data-raw '{
    "originalPartnerReferenceNo": "dwiyogapoint12",
    "serviceCode": "54"
}'
Sample Response
{
   "responseCode": "2005500",
   "responseMessage": "Request has been processed successfully",
   "originalPartnerReferenceNo": "dwiyogapoint12",
   "serviceCode": "54",
   "latestTransactionStatus": "00",
   "refundHistory": [
      {
         "refundNo": "refund-dwiyogapoint12",
         "refundAmount": {
             "value": "1000.00",
             "currency": "IDR"
         },
         "refundStatus": "00",
         "refundDate": "2022-08-04T10:36:46Z"
      }
   ],
   "transAmount": {
      "value": "10000.00",
      "currency": "IDR"
   }
}
Response Code
HTTP status Error code Status Message
200 2005500 SUCCESS Request has been processed successfully
400 4005500 FAILED Bad Request. the originalPartnerReferenceNo field is required
400 4005500 FAILED Bad Request. the serviceCode field is required
400 4005500 FAILED Bad Request. the given serviceCode was invalid
404 4045501 FAILED Transaction Not Found
409 4095500 FAILED Conflict

Token Conversion to SNAP Token (Optional)

This API will be used to convert Payment Token generated from API Linkage V1 to V2/SNAP Access Token and Refresh Token without any relinking activity needed from the users

Request
Method URL
POST /v1/oauth/token/linkageMigrate?grantType=authorization_code
Header
Key
Authorization: Bearer <Linkage V1 Payment Token>
signature
Sample Request
curl --location --request POST '{{HOSTNAME}}/v1/oauth/token/linkageMigrate?grantType=authorization_code \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6Ik56SmhOV0ZpWXpFNFlqRTVORFUzTUdGbE1EUXdOalpqTlRFM05qVXpPREEiLCJyYW5kb20iOiJOVGMyTXpZeU5BIiwidmVyc2lvbiI6MX0.M5WeZ6LyJNJnhnBCiGEfHvvgG1vTE343N1Mp7epSWhg' \
--header 'time: 1652077563147' \
--header 'signature: f5faff203eb6f1c5743dbc56232a4898fa271245df11185df5766bf7c87fe1cb' \
--header 'client-id: test'
Sample Response
{
   "tokens": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6IllUWmxZVFExTkRBd04yVXlORFF4T0RoaVpHTTROall3TmpaaE5tWXhZVEEiLCJyYW5kb20iOiJOak13TURrM05BIiwidmVyc2lvbiI6MX0.gTirMlNU1udcSkhptLvH7LLToZZuJPmmAW2aptlLVYA",
      "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2RlaGFzaCI6IlpqZ3pNakZoTkdFd1kyUmhOR0V3WkdGaU5EWXdNRFU1TnpkaFl6ZGpNamMiLCJyYW5kb20iOiJOak13TURrM013IiwidmVyc2lvbiI6MX0.68PPrwQTC-iMQiPqTdJ3W9aRnnHxUv0pKHaVCPvPjYg",
      "expires_in": 604800,
      "token_type": "Bearer"
   },
   "ilp": "ovo.user.testMerchantBISnap.ADABMQAwATACMwIyAAEACQAFAgAHAg"
}

ILP id is a unique identifier for OVO to identify the user for a particular partner linkage. Partners need to store the ILP id at their end which could be later used to share information relating to a user

  1. It is to be noted that the old v1 tokens (payment and identity tokens) expire 5 minutes after newer tokens are issued.
  2. For signature use specific Hmac
  3. The API is supposed to be used in an incremental way, 1% rollout of existing users followed by a validation, then 5% rollout, 10% and 25%. If sanity yields no negative results, can roll it to 50% and finally 100%
  4. The expected RPS is 20
  5. The token to be used in the authorisation header is supposed to be the old(v1) payment token.