Create and Authenticate Transactions

In Fordefi, a "transaction" is any object that can be signed cryptographically (with a private key).

πŸ“˜

Automate transaction signing

Signing programmatic transactions requires running your own API Signer. Learn more about the feature before you get started with creating transactions.

Create the request

To programmatically create a transaction, you need to call the Create Transaction API by sending a POST request to the /api/v1/transactions endpoint. You can use the following code sample as the basis for any type of transaction. The body of the request differs, based on the specific type of transaction you are creating. Examples appear in the pages of Transaction Types. The mandatory x-signature header is used for an additional layer of authentication for programmatic transactions. See Authenticate Transactions, below.

#!bash
ACCESS_TOKEN='enter your API User Access Token'
PRIVATE_KEY_FILE='private.pem'

ENDPOINT="/api/v1/transactions"
TIMESTAMP="$(($(date +%s) * 1000))"
BODY='{
      ...
}'

SIGNATURE="$(echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}" | openssl dgst -sha256 -sign ${PRIVATE_KEY_FILE} | base64 | tr -d \\n)"

echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}"

curl -v "https://api.fordefi.com${ENDPOINT}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "x-signature: ${SIGNATURE}" \
  -H "x-timestamp: ${TIMESTAMP}" \
  -d "${BODY}"
import requests
import base64
import json
import datetime
import ecdsa
import hashlib


request_json = {...}

access_token = "<API User Access Token>"
request_body = json.dumps(request_json)
private_key_file = "private.pem"
path = "/api/v1/transactions"
timestamp = datetime.datetime.now().strftime("%s")
payload = f"{path}|{timestamp}|{request_body}"

with open(private_key_file, "r") as f:
    signing_key = ecdsa.SigningKey.from_pem(f.read())

signature = signing_key.sign(
    data=payload.encode(), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der
)

resp_tx = requests.post(
    f"https://api.fordefi.com{path}",
    headers={
        "Authorization": f"Bearer {access_token}",
        "x-signature": base64.b64encode(signature),
        "x-timestamp": timestamp.encode(),
    },
    data=request_body,
)

const crypto = require("crypto");
const fs = require('fs');

const gatewayHost = "api.fordefi.com"
const accessToken = "<Enter API User access token>"
const requestJson = {
    ...
};

const requestBody = JSON.stringify(requestJson)
const path = "/api/v1/transactions"
const privateKeyFile = "/private.pem"
const timestamp = new Date().getTime();
const payload = `${path}|${timestamp}|${requestBody}`;

const secretPem = fs.readFileSync(privateKeyFile, 'utf8');
const privateKey = crypto.createPrivateKey(secretPem);
const sign = crypto.createSign('SHA256').update(payload, 'utf8').end();
const signature = sign.sign(privateKey, 'base64');

fetch(`https://${gatewayHost}${path}`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        "Authorization": `Bearer ${accessToken}`,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
    },
    body: requestBody,
}).then((response) => response.text())
  .then((json) => console.log(json));
// Using crate p256 = { version = "0.12.0", features = ["pem"] }
use base64::{engine::general_purpose, Engine as _};
use p256::ecdsa::{signature::Signer, Signature, SigningKey};
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

// This is the main function
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let gateway_host = "api.fordefi.com";
    let access_token = "<Enter API User access token>";

    let request_body = r#"
    {
        ...
    }"#;

    let path = "/api/v1/transactions";
  	let private_key_file = "private.pem";
    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
        .to_string();
    let payload = format!("{path}|{timestamp}|{request_body}");

    let priv_pem = fs::read_to_string(private_key_file).expect("Failed to read pem file");
    let private_key = p256::SecretKey::from_sec1_pem(&priv_pem).expect("Failed to decode pem key");
    let signing_key: SigningKey = private_key.into();

    let signature: Signature = signing_key.sign(payload.as_bytes());
    let formatted_signature = general_purpose::STANDARD.encode(signature.to_der().to_bytes());
    let client = reqwest::blocking::Client::new();
    let res = client
        .post(format!("https://{gateway_host}{path}"))
        .body(request_body)
        .bearer_auth(access_token)
        .header("Content-Type", "application/json")
        .header("X-Timestamp", timestamp)
        .header("X-Signature", formatted_signature)
        .send()?
        .text()?;

    println!("Result: {res}");
    Ok(())
}

Authenticate transactions

API users must strongly authenticate transaction requests that are created programmatically by signing them. (They must also attach their access token to the transaction-creation request, as they do for any other API request.) Note that the signature is an authentication step which is required only for creating transactions. For more information, see Automatic transaction signing.

The signature is passed as part of the x-signature header, and is comprised of the API endpoint, timestamp, and request body signed by the ECDSA private key, that has been previously registered with the API Signer.