本文書は開発者向けドキュメントです。

ユーザーがBuzzvilプロダクトを通じてリワードポイントの付与を受けるには、Buzzvilとパブリッシャー間でポイントシステムの連携が必要です(S2S接続)。本ドキュメントにはポイント付与を実現するためのサーバー連携に関する技術情報を記載しています。

自社ポイントシステムと連携方法の選択

自社ポイントシステムを持っているかどうかによって連携方法が異なります。

下記の手順で連携を行います。

  1. Buzzvilからのポイント付与リクエストを受け取れるサーバーエンドポイントを構築(該当のエンドポイントのURLのを"postback url"とする)

  2. 本ガイドに従ってServer-To-Sever連携を行う

  3. Buzzvil担当者にpostback urlを伝える

パブリッシャーアプリをアメリカまたは複数の国・地域で展開する場合、Buzzvil担当者まで事前にお知らせください。

アプリ内にBuzzvilのポイントシステム(BuzzStore) を連携します。詳しくはBuzzvil担当者までお問い合わせください。

Index

イントロダクション

このAPIはBuzzScreen SDK, BuzzAd SDKを通じてユーザーがリワードポイントの付与を受ける場合において、パブリッシャーにこの事実を伝達し、ポイント付与リクエストを送信するためのものです。

項目

内容

1

送信方向

Buzzvil → パブリッシャー

2

HTTP Request method

POST - application/x-www-form-urlencoded

3

HTTP Request URL

パブリッシャー側で定義

4

HTTP Request Parameters

下記の表を参照

5

HTTP Response Code

パブリッシャー側から受けた応答コードによって該当のPostbackリクエストの成功可否を決定

  • 200 (ok): 成功

    • パブリッシャーサーバーからのリクエストを正常に処理した場合の応答コード

  • その他の応答コード: Buzzvilサーバーは24時間以内に最大5回まで再試行を行います。

    • 再試行を行う時間: 1分後・10分後・1時間後・3時間後・24時間後

Buzzvilサーバーは応答内容(body)を確認しません。リクエストの応答内容にエラーが含まれていても応答コードが200の場合、成功とみなします。

HTTP Request Parameters(リクエストパラメータ)

ポイント付与Postbackはポイントを付与する状況によって下記の2タイプに分けられて転送されます。各タイプ別のフィールド値は下記の表を参照してください。

フィールド

対応

説明

user_id

String 
(max 255)

パブリッシャー側で定義したユーザー識別子

transaction_id

String
(max 32)

ポイント重複付与を防ぐためのid

  • 最大32字まで伝達可能で、連携時に確認が必要です。

★ 同じ transaction_idでリクエストを受け取った場合には必ずポイント重複付与が行われないように処理する必要があります。

point

Integer

ユーザーに付与されるポイントの合計

unit_id

Long

広告枠を区分するID値

現在主に使われている値は15桁の数字です。

title

String
(max 255)

ポイントが付与された方式に設定された名前

  • 広告 (参加した広告のキャンペーン名)

    • e.g まもなく発売の新商品を一足早くチェック!😁 #Buzzvil #オトク

  • その他はすべて空の値になる

    • ex. 万歩計/Potto ( (blue star) 現在日本では対象なし)

最大文字長は255字です。 実装時にDB別の文字長を参照してください。

action_type

String
(max 32)

ポイントの付与を受けるためにユーザーが行ったアクション

  • u: ロック解除

  • l: LP遷移

  • a: アクション(広告の指定アクションが実行された時)

note

今後新しいタイプが追加される可能性があります。

今後新しいタイプが追加される可能性があります。

event_at

Long (timestamp)

ポイント付与時点 (UNIX Timestamp 秒単位)

note

通常はAPI呼び出し時点と同一ですが、再試行の場合は異なることがあります。

通常はAPI呼び出し時点と同一ですが、再試行の場合は異なることがあります。

extra

String
(max 1024)

パラメータを追加した場合、json serializeされた文字列を表示

  • パブリッシャーが定義したキャンペーンデータが(管理画面上でキャンペーンを設定した場合に指定可能です)

    • e.g {"sub_type": "A", "source":"external"}

data

String

HTTP request parameterを暗号化して転送する場合に使用するパラメータ

詳細については下記 HTTP Request Parameter Encryption/Decryptionを参照してください。

リクエストパラメータの検証

1. HTTP Request Parameterの暗号化と復号

Postbackの際、Buzzvilサーバーからパブリッシャーのサーバーにパラメータを送信します。このパラメータの暗号化を希望する場合は下記の手順で行います。なお、この設定は任意であり、必須ではありません。

提供可能な暗号化方式は暗号化の標準であるAES(Advanded Encryption Standard)です。提供されるブロック暗号化はAES-256 で、ブロック暗号化モード(Block Cipher)はCBC(Cipher Block Chaning) Modeと PKCS7を使用します。

手順

  1. 暗号化キーの発行

    1. Buzzvil担当者との事前協議

    2. Buzzvil担当者に暗号化キー(AES key, IV 値)の発行を依頼する

  2. Buzzvil serverにて下記順序でパラメータを暗号化

    1. JSON serialized parameters with UTF-8 encoding

    2. AES(CBC mode, PKCS7 padding) encryption

    3. base64 encoding

  3. HTTP POST requestにdataパラメータを追加した状態で暗号化されたデータを送信

  4. 受信側(パブリッシャー)は HTTP POST requestにてdata パラメータを取得し、下記順序で復号する

    1. base64 decoding

    2. AES decoding

    3. JSON load

暗号化と復号化の例

  • 元データ(Original)
    例ではAES key, IV 値としてすべて 12341234asdfasdfを使用

{
    "event_at": 1442984268, 
    "user_id": "testuserid76301", 
    "action_type": "u", 
    "extra": "{}", 
    "is_media": 0, 
    "base_point": 2, 
    "point": 2, 
    "campaign_name": "test campaign", 
    "campaign_id": 3467, 
    "transaction_id": 429482977
}
  • 暗号化の結果

    • JSON (UTF-8encoding) → AES encryption → base64 encoding の順に行う

      sgfHOC5Z66tLmlokmQEaXY39u+64gMWhLnxQAZ9ivYsTvF1isjVfaRx2BNhOADwPR6KB55/7F7iXBm5FKU8mHmHnlR3wSomVAlcjtx77KluoYoXi/jRCvaFLGIo7vcK1GVHxS557u/XTo53/AzdPZpk/aXkvFZvWPgS+GWj1TWle0mBJ0xOgfmb8LwMfi4rvfayTph3bZeryLuphorBzMoIhf+kQLyjfIyouWVoCh6UICeRBgzTS9SlgdUA6M1PVlCsQch0zKVeTJZEFEn8478QbpEEhgHDhXkzdo8tXgkw=
  • 復号するには

    • base64 decoding)

      \xb2\x07\xc78.Y\xeb\xabK\x9aZ$\x99\x01\x1a]\x8d\xfd\xbb\xee\xb8\x80\xc5\xa1.|P\x01\x9fb\xbd\x8b\x13\xbc]b\xb25_i\x1cv\x04\xd8N\x00<\x0fG\xa2\x81\xe7\x9f\xfb\x17\xb8\x97\x06nE)O&\x1ea\xe7\x95\x1d\xf0J\x89\x95\x02W#\xb7\x1e\xfb*[\xa8b\x85\xe2\xfe4B\xbd\xa1K\x18\x8a;\xbd\xc2\xb5\x19Q\xf1K\x9e{\xbb\xf5\xd3\xa3\x9d\xff\x037Of\x99?iy/\x15\x9b\xd6>\x04\xbe\x19h\xf5Mi^\xd2`I\xd3\x13\xa0~f\xfc/\x03\x1f\x8b\x8a\xef}\xac\x93\xa6\x1d\xdbe\xea\xf2.\xeaa\xa2\xb0s2\x82!\x7f\xe9\x10/(\xdf#*.YZ\x02\x87\xa5\x08\t\xe4A\x834\xd2\xf5)`u@:3S\xd5\x94+\x10r\x1d3)W\x93%\x91\x05\x12\x7f8\xef\xc4\x1b\xa4A!\x80p\xe1^L\xdd\xa3\xcbW\x82L

       

    • AES decryption) Key, IV: 12341234asdfasdf

      {"event_at": 1442984268, "user_id": "testuserid76301", "action_type": "u", "extra": "{}", "is_media": 0, "base_point": 2, "point": 2, "campaign_name": "test campaign", "campaign_id": 3467, "transaction_id": 429482977}

       

    • UTF-8 decoding

      {"event_at": 1442984268, "user_id": "testuserid76301", "action_type": "u", "extra": "{}", "is_media": 0, "base_point": 2, "point": 2, "campaign_name": "test campaign", "campaign_id": 3467, "transaction_id": 429482977}

      (memo) 上の例では日本語文字が含まれていないためUTF-8 decoding を行わなくても同じ結果が得られます

開発言語別のコード例は下記のとおりです。

import java.lang.RuntimeException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.*;
import javax.crypto.spec.SecretKeySpec;

public class Main {
    public static void main(String[] args) {
        String aesKey = "BuzzvilAESKeyTest123456789101112";
        String aesIV = "0000000000000000";

        // Check to make sure encryption is compatible with Buzzvil
        String toBuzzvilMessage = "{\"success\": 1, \"reason\": \"중복 적립 요청\"}";
        String encrypted = encrypt(toBuzzvilMessage, aesKey, aesIV);
        System.out.println("----encrypted----\n"+encrypted);
        System.out.println("buzzvil encryption compatible? YES");

        // Check to make sure values coming from Buzzvil is decryptable
        String fromBuzzvilEncrypted = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw==";
        String fromBuzzvilDecrypted = "{\"point\": 1, \"user_id\": \"buzzvil_test\", \"transaction_id\": \"100004_100000000\", \"event_at\": 1588936508, \"campaign_name\": \"버즈빌 테스트 campaign_name\", \"extra\": \"{}\", \"action_type\": \"l\", \"base_point\": 1, \"campaign_id\": 202010160022, \"is_media\": 1, \"unit_id\": 452613281179508, \"revenue_type\": \"cpm\"}";

        String decrypted = decrypt(fromBuzzvilEncrypted, aesKey, aesIV);
        System.out.println("----decrypted-----\n"+decrypted);
        System.out.println("buzzvil decryption compatible? "+fromBuzzvilDecrypted.equals(decrypted));
    }

    static String encrypt(String message, String key, String iv) {
        try {
            // 1. encode in utf-8
            byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

            // 2. encrypt message through AES CBC with PKCS7
            byte[] encrypted = aes(messageBytes, key, iv, Cipher.ENCRYPT_MODE);

            // 3. encode in base64
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    static String decrypt(String message, String key, String iv) {
        try {
            // 1. decode base64 message
            byte[] base64DecodedBytes = Base64.getDecoder().decode(message);

            // 2. decrypt message through AES CBC with PKCS7
            byte[] decrypted = aes(base64DecodedBytes, key, iv, Cipher.DECRYPT_MODE);

            // 3. decode in utf-8
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    static byte[] aes(byte[] messageBytes, String key, String iv, int cipherMode) {
        try {
            byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
            byte[] ivBytes = iv.getBytes(StandardCharsets.UTF_8);

            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(cipherMode, keySpec, ivSpec);

            return cipher.doFinal(messageBytes);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new RuntimeException(e);
        }
    }
}
<?php
function encrypt($str, $mode, $key, $iv) {
    $encrypted = openssl_encrypt($str, $mode, $key, OPENSSL_RAW_DATA, $iv);
    return base64_encode($encrypted);
}
 
function decrypt($str, $mode, $key, $iv) {
    $text = base64_decode($str);
    return openssl_decrypt($text, $mode, $key, OPENSSL_RAW_DATA, $iv);
}
 
$mode = "AES-256-CBC";
 
$key = "BuzzvilAESKeyTest123456789101112";
$iv = "0000000000000000";

$plaintext = '{"success": 1, "reason": "중복 적립 요청"}';
$encrypted = encrypt($plaintext, $mode, $key, $iv);
print ("ENCRYPTED: ".$encrypted."\n");
// +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==

$fromBuzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw==";
$decrypted = decrypt($fromBuzzvil, $mode, $key, $iv);
print("DECRYPTED: ".$decrypted);
/*
{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}
*/
?>

Python 2.7+

# -*- coding:utf-8 -*-
import base64
# https://pypi.python.org/pypi/pycryptodome/3.9.9
from Crypto.Cipher import AES
# https://pypi.python.org/pypi/pkcs7/0.1.2
from pkcs7 import PKCS7Encoder

def encrypt(message, key, iv):
	message_plaintext_padded = PKCS7Encoder().encode(message)
	cipher = AES.new(key, AES.MODE_CBC, iv)
	message_encrypted_raw = cipher.encrypt(message_plaintext_padded)
	return base64.b64encode(message_encrypted_raw)

def decrypt(message, key, iv):
	message_decoded = base64.b64decode(message)
	cipher = AES.new(key, AES.MODE_CBC, iv)
	messaged_decrypted_padded = cipher.decrypt(message_decoded)
	message_plaintext_decoded = PKCS7Encoder().decode(messaged_decrypted_padded)	
	return message_plaintext_decoded

iv = '0000000000000000'
key = 'BuzzvilAESKeyTest123456789101112'

plaintext = '{"success": 1, "reason": "중복 적립 요청"}'
encrypted = encrypt(plaintext, key, iv)
print "ENCRYPTED: {}".format(encrypted)
# +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==

from_buzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw=="
decrypted = decrypt(from_buzzvil, key, iv)
print "DECRYPTED: {}".format(decrypted)
#{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}

Python 3.6+

# -*- coding:utf-8 -*-
import base64
# https://pypi.python.org/pypi/pycryptodome/3.9.9
from Crypto.Cipher import AES
from binascii import unhexlify

BLOCK_SIZE = 16

def encrypt(message, key, iv):
	message_plaintext_padded = pad(message).encode('utf-8')
	cipher = AES.new(key, AES.MODE_CBC, iv)
	message_encrypted_raw = cipher.encrypt(message_plaintext_padded)
	return base64.b64encode(message_encrypted_raw)

def decrypt(message, key, iv):
	message_decoded = base64.b64decode(message)
	cipher = AES.new(key, AES.MODE_CBC, iv)
	messaged_decrypted_padded = unpad(cipher.decrypt(message_decoded))
	return messaged_decrypted_padded.decode('utf-8')

def pad(s):
	return s + (BLOCK_SIZE - len(s.encode('utf-8')) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s.encode('utf-8')) % BLOCK_SIZE)

def unpad(s):
	return s[:-ord(s[len(s)-1:])]

iv = '0000000000000000'.encode('utf-8')
key = 'BuzzvilAESKeyTest123456789101112'.encode('utf-8')

plaintext = '{"success": 1, "reason": "중복 적립 요청"}'
encrypted = encrypt(plaintext, key, iv)
print ("ENCRYPTED: {}".format(encrypted))
# +VEmHrt+jwI6Dg2zImdGtI+iIQEqV8v5btpS1a3cdEQBzIc72V9aKju5m6+ELTBixbITMBoHIYjj8jJbsKbIgg==

from_buzzvil = "IGCdundUBkXf3s7VXl0pqIKDSC/KGc2j8n1DBLKLZAHqkYlG+aWW+G5hGLvoNeUjlI42FtJLpwGUYbFlhy0QXLQv1Z+P7iUOyJrhujmFWX1FdJ5ZBefA5aceGiOlN119NPAX3JOuUAf45HkWG52NcdaHOzWu8rTnghSeLPo9QK0t6l/2gSFvGtOfZolnAHNZAeGEmcqAkhPmUoFtRAW+Zh6TNQY68FrSUI/XYc87Ky0ndaug1Kf7Ogbf8zLK+tJ4LdTCn9A+wcWxEpdkX45f1r/8jTIUK/s1PqBirXFuruq5/XhkhFmdq/I0qBAJ0uxBnk+29GaEQVMtYTzB+eJWTgrQzKhN6Nww2XEPEOl27yH+K0F+sj8QpZ0jkPETadP0gpwKMKv3zlA6xyndIYWrpw=="
decrypted = decrypt(from_buzzvil, key, iv)
print ("DECRYPTED: {}".format(decrypted))
#{"point": 1, "user_id": "buzzvil_test", "transaction_id": "100004_100000000", "event_at": 1588936508, "campaign_name": "버즈빌 테스트 campaign_name", "extra": "{}", "action_type": "l", "base_point": 1, "campaign_id": 202010160022, "is_media": 1, "unit_id": 452613281179508, "revenue_type": "cpm"}