πŸ”” Webhook notifications

This section covers server-to-server communication that happens in case of some events

General information

If you are interested in receiving webhook notifications, please provide your webhook endpoint URL to [email protected].

As a user of the TGB API, you can subscribe to some events that happen during the donation process. TGB has implemented event notifications as HTTP Webhooks.

Security Measures:

  • payload field is AES encrypted
  • all events’ payloads will contain eventTimestamp field, so consumer may implement timestamp check, i.e. to not accept if message is older than 1 hour and/or emit an security alert in this case

Below you can find information about different types of webhooks TGB sends to subscribers.

Payload decryption

To decrypt the payload you need a pair of "encryption key" and "iv" shared with you during your API credentials creation.

You can find some code snippets below. Given:

Encryption key: ae97bfa25b4abcbb7d8722fc5c60747bf7687ec22d1463174f0176ad6daa96ec
Encryption IV: 9663f5821170b172c51ecdd86677132f
Encrypted body you've received: 87afbab0f01b956c445b00d3ab1ecf09abf18e1d1ab980641a035ec801deff07d7b708d5f2747469b86ce6216358cb695df8c7d005265aab66f678d36f01af333f6f8715569c39adabf2688cdaabac3825a0ea2b588f7e083fcf62736a3064d395bed377254302f6279ec0550f117f63c971cc9320674f535694a27fc1f034cee57433c00fbec7cdd5d2daa5b12abfd96039715c98e8e8bca401011278c5c0d3ed30f710b11a1a9b8c67773eb34f0b050acbf97b32445e5bcc32399319bd947c42d1f9ba6051f9e998dfe2c61486af0e8584529d94fc3afda941557c58725aa9c7af03577446c6d6ff2e0894cef125c0e2366a3a96f11db5a4961ee92c8262b86836f6f8debf71c80e6b36a1bfd43cff72f144560258780f8bd97100e4e4b7cb9ef45d7b61133c3b197a2e12787e439ff197e2b567a3e178b9ff7155e2663989606cdc630dd835651ad18ab88f9494efc76a74e51ff01127ac4d83b3dd6f56cea34488ff8df204d470602a40cc970f5b1cfcf6c741abe20df05b392db817903191b6fc1a1b798cc369c5a76d151cbeaa

Code examples that allow to decipher the encrypted body:

Decryption code snippet:  
import crypto from "crypto";

const encryptedPayloadHex = '87afbab0f01b956c445b00d3ab1ecf09abf18e1d1ab980641a035ec801deff07d7b708d5f2747469b86ce6216358cb695df8c7d005265aab66f678d36f01af333f6f8715569c39adabf2688cdaabac3825a0ea2b588f7e083fcf62736a3064d395bed377254302f6279ec0550f117f63c971cc9320674f535694a27fc1f034cee57433c00fbec7cdd5d2daa5b12abfd96039715c98e8e8bca401011278c5c0d3ed30f710b11a1a9b8c67773eb34f0b050acbf97b32445e5bcc32399319bd947c42d1f9ba6051f9e998dfe2c61486af0e8584529d94fc3afda941557c58725aa9c7af03577446c6d6ff2e0894cef125c0e2366a3a96f11db5a4961ee92c8262b86836f6f8debf71c80e6b36a1bfd43cff72f144560258780f8bd97100e4e4b7cb9ef45d7b61133c3b197a2e12787e439ff197e2b567a3e178b9ff7155e2663989606cdc630dd835651ad18ab88f9494efc76a74e51ff01127ac4d83b3dd6f56cea34488ff8df204d470602a40cc970f5b1cfcf6c741abe20df05b392db817903191b6fc1a1b798cc369c5a76d151cbeaa';  
const dataEncryptionKey = Buffer.from('ae97bfa25b4abcbb7d8722fc5c60747bf7687ec22d1463174f0176ad6daa96ec', 'hex');  
const dataEncryptionKeyIV = Buffer.from('9663f5821170b172c51ecdd86677132f', 'hex');

const decryptText = (hexEncodedString: string) => {  
  const encryptedBuffer = Buffer.from(hexEncodedString, 'hex');  
  const decipher = crypto.createDecipheriv('aes-256-cbc', dataEncryptionKey, dataEncryptionKeyIV);

  const decrypted = decipher.update(encryptedBuffer);

  return Buffer.concat([decrypted, decipher.final()]).toString();  
}

console.log(decryptText(encryptedPayloadHex));
Decryption code snippet

Copy
<?php
$hexEncryptedPayload   = '87afbab0f01b956c445b00d3ab1ecf09abf18e1d1ab980641a035ec801deff07d7b708d5f2747469b86ce6216358cb695df8c7d005265aab66f678d36f01af333f6f8715569c39adabf2688cdaabac3825a0ea2b588f7e083fcf62736a3064d395bed377254302f6279ec0550f117f63c971cc9320674f535694a27fc1f034cee57433c00fbec7cdd5d2daa5b12abfd96039715c98e8e8bca401011278c5c0d3ed30f710b11a1a9b8c67773eb34f0b050acbf97b32445e5bcc32399319bd947c42d1f9ba6051f9e998dfe2c61486af0e8584529d94fc3afda941557c58725aa9c7af03577446c6d6ff2e0894cef125c0e2366a3a96f11db5a4961ee92c8262b86836f6f8debf71c80e6b36a1bfd43cff72f144560258780f8bd97100e4e4b7cb9ef45d7b61133c3b197a2e12787e439ff197e2b567a3e178b9ff7155e2663989606cdc630dd835651ad18ab88f9494efc76a74e51ff01127ac4d83b3dd6f56cea34488ff8df204d470602a40cc970f5b1cfcf6c741abe20df05b392db817903191b6fc1a1b798cc369c5a76d151cbeaa';
$cipher                = 'aes-256-cbc';
$hexEncryptedKey       = hex2bin( 'ae97bfa25b4abcbb7d8722fc5c60747bf7687ec22d1463174f0176ad6daa96ec' );
$hexEncryptedIV        = hex2bin( '9663f5821170b172c51ecdd86677132f' );

// Decrypt the payload.  NOTE this requires the Open SSL module installed on your server
// Also make sure that this function is accepting a RAW encoded string and NOT a base64 encoded string
// This is easily handled by making the 4th parameter TRUE
$decrypted_data      = openssl_decrypt( hex2bin( $hexEncryptedPayload ), $cipher, $hexEncryptedKey, true, $hexEncryptedIV );
echo $decrypted_data;

After decryption you should get:

{  
  "id": "6a82de8c-d7ae-4db5-972b-588808d5f111",  
  "type": "Deposit",  
  "status": "Complete",  
  "timestampms": "1640774872000",  
  "eid": "123456789",  
  "transactionHash": "0ccb68148928602274f94d1119bb5b3ac705e5b567396b948e4eb4cb12ee358996",  
  "currency": "ETH",  
  "amount": 1.35,  
  "organizationId": 1234567,  
  "eventTimestamp": 1640778972000,  
  "pledgeId": "e4a096e9-07e4-4a34-9e52-fff72fd27260",  
  "valueAtDonationTimeUSD": 4159.42  
}

Event: Deposit completed

When the deposit transaction is completed, The Giving Block will detect that and this notification will be sent as HTTP POST request to your notifications URL if enabled for your API user.

POST Body example:

πŸ“˜

Note: that β€œpayload” will be passed to you in encrypted way. POST Body of the webhook will look like

{  
  "eventType": "DEPOSIT_TRANSACTION",  
  "payload": "hex-encrypted-string"  
}
{  
  "eventType": "DEPOSIT_TRANSACTION",  
  "payload": {  
    "type":  "Deposit",  
    "id":  "8f2191c1-d28c-40ca-83b0-481828cbd8d0",  
    "status": "Advanced | Complete",  
    "timestampms":  "1507913541275", // The time that the trade was executed in milliseconds  
    "eid": "309356152", // Transfer event id, unique identifier on the exchange side 
    "transactionHash": "0x12344567898909123124125125abcdefabcdefabcdef",  
    "currency":  "ETH",  
    "amount": 36.00,  
    "organizationId": 1234567, // Nonprofit organization identifier on The Giving Block side
    "eventTimestamp": "15079132214598",
    "pledgeId": "fe497575-cf4b-4eec-b7e3-b6ce75298bdb",  
    "valueAtDonationTimeUSD": 45000,  
    "paymentMethod": "Crypto",  
    "payoutAmount": 35.00,  
    "payoutCurrency": "USD",  
    "externalId": "my-transaction-id",
    "grossAmountInPayoutCurrency": 35.00,
  }  
}

Explanation on some notification fields

  • status
  • pledgeId is an unique identifier of a pledge. You can use this id to match the donation address you received on CreateDepositAddress API call.
  • valueAtDonationTimeUSD is a USD equivalent of transaction's amount at the time of donation. Please note that this amount is before any fees are withdrawn and should not be used for any calculation. This is primarily used for nonprofits who are HODLing and not immediately converting. This means that organizations can get approximate donation amount, in case autoconversion is disabled for the organization.
  • paymentMethod - returns donation payment method. Can be one of these: Crypto, Card, Stock.
  • transactionHash - transaction hash or ID on the blockchain.
  • payoutAmount - an amount in currency returned as payoutCurrency that the nonprofit will receive as their final balance (gross donation amount - total fees). For crypto donations the amount will be provided once transaction is converted.
  • payoutCurrency - the currency for payoutAmount.
  • externalId - the nonprofit ID (optional) that was passed as a parameter while retrieving Widget URL (see "Widget URL" API reference for more details).

Event: Transaction converted

This event is being sent only for crypto donations.

When the transaction is converted, The Giving Block will detect that and this notification will be sent as HTTP POST request to your notifications URL if enabled for your API user.

POST Body example:

πŸ“˜

Note: β€œpayload” will be passed to you in encrypted way. POST Body of the webhook will look like

{  
  "eventType": "TRANSACTION_CONVERTED",  
  "payload": "hex-encrypted-string"  
}
{  
  "eventType": "TRANSACTION_CONVERTED",  
  "payload": {  
    "type":  "Deposit",  
    "id":  "8f2191c1-d28c-40ca-83b0-481828cbd8d0",  
    "status": "Advanced | Complete",  
    "timestampms":  "1507913541275", // The time that the trade was executed in milliseconds  
    "eid": "309356152", // Transfer event id  
    "transactionHash": "0x12344567898909123124125125abcdefabcdefabcdef",  
    "currency": "BTC",  
    "amount": 1.00,  
    "organizationId": 1234567,  
    "eventTimestamp": 15079132214598,  
    "convertedAt": "16079132214598",  
    "netValueAmount": 49000.00,  
    "grossAmount": 50000,  
    "netValueCurrency": "USD",  
    "pledgeId": "fe497575-cf4b-4eec-b7e3-b6ce75298bdb",  
    "valueAtDonationTimeUSD": 50000,  
    "payoutAmount": 49000.00,  
    "payoutCurrency": "USD",  
    "externalId": "my-transaction-id",
    "grossAmountInPayoutCurrency": 50000.00
  }  
}

Explanation on some fields

  • netValueAmount is an amount in currency returned as netValueCurrency that the nonprofit will receive as their final balance (gross donation amount - total fees).
  • grossAmount is an amount that is calculated during conversion of donated crypto.
  • valueAtDonationTimeUSD is a USD equivalent of transaction's amount at the time of donation. Please note that this amount is before any fees are withdrawn and should not be used for any calculation. This is primarily used for nonprofits who are HODLing and not immediately converting. This means that organizations can get approximate donation amount, in case autoconversion is disabled for the organization.
  • payoutAmount is an amount in currency returned as payoutCurrency that the nonprofit will receive as their final balance (gross donation amount - total fees).
  • payoutCurrency the currency for payoutAmount.
  • externalId the nonprofit ID (optional) that was specified as a parameter while retrieving Widget URL (see "Widget URL" API reference for more details).

Example:
A donor made a donation of 1 BTC, when the BTCUSD exchange rate is 50,000 USD.

  • The donation amount equals 1 BTC
  • valueAtDonationTimeUSD is 50,000 USD (amount * BTCUSD rate)
  • For example, TGB converts the donation with average exchange rate of 50,000 USD per 1 BTC.
  • Before the conversion happens, all fees are withdrawn. In this example, there is a 2% fee applied so the amount to convert to USD is 0.98 BTC (1 BTC - total fees).
  • Therefore, netValueAmount will be calculated by taking 0.98 BTC * 50,000 USD = 49,000 USD
  • So at the end of donation processing, the nonprofit organization will have 49,000 USD as their balance which will then be transferred to their bank account.

Event: Merchant status events

After onboarding a merchant to receive credit card donations, all the onboarding data will be reviewed by underwriting team. Any merchant status update will be sent as a HTTP POST request to your notification URL if enabled for your API user.

POST body example:

πŸ“˜

Note: that β€œpayload” will be passed to you in encrypted way. POST Body of the webhook will look like

{  
  "eventType": "DEPOSIT_TRANSACTION",  
  "payload": "hex-encrypted-string"  
}
{  
  "eventType": "MERCHANT_STATUS_EVENT",  
  "payload": {  
    "timestamp": 1709063291321,  
    "eventType": "MERCHANT_STATUS_EVENT",  
    "mid": "11223344556677",  
    "status": "600",  
    "statusChangeReason": "Reviewed.",  
    "officeId": "S4TGB"  
  }  
}

Explanation of fields:

  • timestamp - time of the event in milliseconds
  • eventType - event type (will always be MERCHANT_STATUS_EVENT)
  • mid - Merchant ID
  • status - merchant status. Statuses are listed below
  • statusChangeReason - brief description of status
  • officeId - Office ID, service variable that we use to differentiate customers

Possible merchant statuses:

Status #Status name
125RESUBMITTED APPLICATION
200DECLINED
300CLOSED - MERCHANT CANCELLED
325CLOSED - TERMINATION
330SEASONAL BUSINESS
350GATEWAY / ACCESSORY SERVICE ONLY
375AWAITING ISO ACTIVATION OF UNDERWRITING
390ORIG. APP RECEIVED - IN UNDERWRITING
400IN UNDERWRITING
425ON PRIMARY HOLD - NEED ADDITIONAL INFORMATION
500APPROVED - IN DATA ENTRY FOR MID BUILD
550APPROVED - AWAITING FAXED DOCUMENTATION
600APPROVED
650APPROVED - INSTALLED
675MERCHANT REQUESTING TO CANCEL
700APPROVED - PROCESSING
730APPROVED - SEASONAL HOLD
785LEGAL REVIEW